MySQL之事务、锁

一、概念

  锁是计算机协调多个进程或线程访问某一个资源的机制。在数据库中,除传统的计算资源(CPU、RAM、IO)的争用意外,数据也是一种许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问的一个重要因素。从这个角度来说,锁对数据库尤其重要,也更加复杂。本文就以MyISAM和InnoDB两个引擎来说明锁的问题;

二、MySQL锁概述

  相对其他数据库而言,MySQL的锁机制比较简单,其中最为显著的特点是不同的引擎具有不同的锁。比如MyISAM和InnoDB分别采用表锁、行锁,但是InnoDB也支持表锁,默认情况下采用行锁;

三、MySQL表锁和行锁对比

锁的名称 开销 加锁速度 死锁 粒度 并发性能
表锁 不会出现 最低
行锁 出现 最高

从锁的角度来说表锁适合查询为主,只有少量按索引条件更新数据的应用;行锁适合于大量按索引条件来并发更新少量的数据,同时又有并发查询的应用;

MyISAM表锁

一、查询表级锁的争用情况

  可以通过table_locks_waited和table_locks_immediate来分析系统表锁的争夺;

如果table_locks_waited的值比较高的话,则说明存在较高的表锁争夺。

二、表锁的锁模式

  表级锁有两种方式:读锁和写锁,锁模式的兼容性如下:

 请求锁模式是否兼容当前锁模式  读锁  写锁
 读锁  是  否
 写锁  否  否

由上表可见,MyISAM表的读操作是不会阻塞其他用户对同一张表的读请求,但会阻塞表的写请求;对于MyISAM的写操作,则会阻塞其他用户对同一张表的读和写操作。MyISAM的读和写操作之间,写操作和写操作之间是串行的。也就是说当一个线程获得一个表的写锁之后,只有持有锁的线程可以执行更新操作,其他线程的读和写都要等待,直到锁被释放为止。

三、如何加表锁

  MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。

  给MyISAM表加显示锁,一般是在一定程度上模拟事务操作,实现某一时间点多个表大的一致性读取。例如我们现在有一个库存表和入库单表,我们要判断入库单的总数是否和库存表的一致,这个时候我们不给这两个表加锁,就可能产生错误的结果,比如在执行第一次查询库存的时候,入库单表发生了改变。这个时候就造成数据错误。

  对以上的列子做出总结:

  1、上面的例子在LOCK TABLES时加了“local”选项,其作用就是在满足MyISAM表并发插入条件的情况下,允许其他用户在表尾并发插入记录;

  2、在用LOCK TABLES给表显式加表锁时,必须同时取得所有涉及到表的锁,并且MySQL不支持锁升级。也就是说,在执行LOCK TABLES后,只能访问显式加锁的这些表,不能访问未加锁的表;同时,如果加的是读锁,那么只能执行查询操作,而不能执行更新操作。其实,在自动加锁的情况下也基本如此,MyISAM总是一次获得SQL语句所需要的全部锁。这也正是MyISAM表不会出现死锁的原因。

四、并发插入 

  MyISAM的存储引擎有一个系统的变量concurrent_insert,专门用于控制并发插入的行为,其值分别是0,1,2:

  1、 当concurrent_insert设置为0时,不允许并发插入。

  2、 当concurrent_insert设置为1时,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置。

  3、当concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录。

五、MyISAM的锁调度

  MyISAM的存储引擎的读锁和写锁互斥的,读写操作都是串行的。比如现在有一个进程某一个表的读锁,这是同时有个进程来请求同一张表的写锁,这个时候MyISAM表会怎么做呢?结果就是写锁先获取,不仅如此,如果读锁先进入队列,写锁后进入队列,这个时候也是写锁现货的。这是因为MySQL认为写比读重要。这也正是MyISAM表不太合适有大量更新操作和查询操作的原因。因为大量的更新操作是的读操作难以获得锁,从而可能永远阻塞,这种情况可能会变的非常糟糕。但是我们可以设置MyISAM的调度行为

1、 通过指定启动参数low-priority-updates,使MyISAM引擎默认给予读请求以优先的权利。

2、通过执行命令SET LOW_PRIORITY_UPDATES=1,使该连接发出的更新请求优先级降低。

3、通过指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先级。

4、给系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL就暂时将写请求的优先级降低,给读进程一定获得锁的机会。

5、一些需要长时间运行的查询操作,也会使写进程阻塞。

六、什么时候用表锁

1、事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度。

2、事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。

InnoDB锁

  InnoDB和MyISAM的区别是,InnoDB支持事务和行级锁。行级锁和表锁有很多不同的地方,事务的引入也带来了一些新的问题。

一、事务

1、事务的特性

  原子性对数据的修改,要么全部成功,要么全部失败。

  一致性:在事务开始的时候和完成的时候,数据都必须保持一致的状态。

  隔离性:保证事务在不受外部并发操作影响的“独立”环境执行。也就是事务处理过程的中间部分是不可见的。

  持久性:事务完成之后,他对数据的修改是永久的,即系统出现故障也可以保持。

2、并发事务带来的问题

  更新丢失:最后的更新覆盖了其他事务所做的更新。解决此问题的条件是,当前用户在没有提交事务的时候,其他用户是不可以操作的。

  脏读:一个事务正在对一条记录做修改,这个事务在完成提交前,这个时候另一个事务来读取这一行的数据,就有可能读到脏数据。

  不可重复读:一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!

  幻度:一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据。

3、事务的隔离级别

  a、更新丢失通常应该完全可以避免,但防止更新丢失,不能单靠数据库事务控制来解决,需要应用程序对更新的数据加锁来解决;

  b、“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决

隔离级别 读数据一致性 脏读 不可重复读 幻读
未提交读(Read uncommitted) 最低级别,只能保证不读取物理上损坏的数据
已提交度(Read committed) 语句级
可重复读(Repeatable read) 事务级
可序列化(Serializable) 最高级别,事务级

4、InnoDB的行锁模式和加锁方法

  

锁类型 可读(其他事务) 可写(其他事务)
共享锁(S)
排他锁(x)

为了行锁和表锁共从,InnoDB又提供了两个意向锁(也就是表锁)

  意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。

  意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

这四种锁之间的兼容性如下:

锁模式 S X IS IX
S 兼容 冲突 兼容 冲突
X 冲突 冲突 冲突 冲突
IS 兼容 冲突  兼容 兼容
IX  冲突  冲突 兼容   兼容

如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。

意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;

5、InnoDB行锁的实现方式

  InnoDB的行锁实现是通过给索引上的索引项加锁来实现的;也就说只有通过索引检索数据,InnoDB才使用行级锁,否则InnoDB将使用表锁。

注意点:

  a、在不通过索引条件查询的时候,InnoDB确实使用的是表锁,而不是行锁。

  b、由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。

  c、当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。

  d、即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。

6、间隙锁

  当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。

目的

  InnoDB使用间隙锁的目的,一方面是为了防止幻读,以满足相关隔离级别的要求,如果不使用间隙锁,如果其他事务插入了超过范围的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;另外一方面,是为了满足其恢复和复制的需要。

  很显然,在使用范围条件检索并锁定记录时,InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待。因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。

死锁

  发生死锁后,InnoDB一般都能自动检测到,并使一个事务释放锁并回退,另一个事务获得锁,继续完成事务。但在涉及外部锁,或涉及表锁的情况下,InnoDB并不能完全自动检测到死锁,这需要通过设置锁等待超时参数 innodb_lock_wait_timeout来解决。需要说明的是,这个参数并不是只用来解决死锁问题,在并发访问比较高的情况下,如果大量事务因无法立即获得所需的锁而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖跨数据库。我们通过设置合适的锁等待超时阈值,可以避免这种情况发生。

  通常来说,死锁都是应用设计的问题,通过调整业务流程、数据库对象设计、事务大小,以及访问数据库的SQL语句,绝大部分死锁都可以避免。下面就通过实例来介绍几种避免死锁的常用方法。

  1、在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会。

  2、在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低出现死锁的可能。

  3、在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁,更新时再申请排他锁,因为当用户申请排他锁时,其他事务可能又已经获得了相同记录的共享锁,从而造成锁冲突,甚至死锁。

  4、在REPEATABLE-READ隔离级别下,如果两个线程同时对相同条件记录用SELECT...FOR UPDATE加排他锁,在没有符合该条件记录情况下,两个线程都会加锁成功。程序发现记录尚不存在,就试图插入一条新记录,如果两个线程都这么做,就会出现死锁。这种情况下,将隔离级别改成READ COMMITTED,就可避免问题

  5、当隔离级别为READ COMMITTED时,如果两个线程都先执行SELECT...FOR UPDATE,判断是否存在符合条件的记录,如果没有,就插入记录。此时,只有一个线程能插入成功,另一个线程会出现锁等待,当第1个线程提交后,第2个线程会因主键重出错,但虽然这个线程出错了,却会获得一个排他锁!这时如果有第3个线程又来申请排他锁,也会出现死锁。

原文地址:https://www.cnblogs.com/meichao/p/9263153.html

时间: 2024-11-08 20:38:45

MySQL之事务、锁的相关文章

mysql 中的 latch锁和Tlock(事务锁), DML加锁规则,以及死锁分析。

一.Latch和Tlock的关系 Latch:为保护临界资源的正确性而设计,例如保护正在使用的内存页面不被破坏等. 没有死锁检测机制,轻量锁,并且作用对象时内存页面或是内存共享变量. Tlock:事务锁,作用对象是事务,有死锁检测机制. 在innodb内部,为了减少死锁的发生概率,Latch不会等待Tlock. 线程获取行锁的流程: 在对行加锁的时候会先对行所在的页面添加lath,然后再对行添加Tlock,待对行添加完Tlock后再释放页面的Lath. 这种机制主要是为了保证线程获取的行数据的一

Python进阶----pymysql的安装与使用,mysql数据库的备份和恢复,mysql的事务和锁

Python进阶----pymysql的安装与使用,mysql数据库的备份和恢复,mysql的事务和锁 一丶安装 pip install PyMySQL 二丶pymysql连接数据库 ### 语法: import pymysql # 导入py模块 conn= pymysql.connect("数据库ip","用户","密码","数据库" ) # 打开数据库连接 cursor.execute("SELECT VERSI

MySQL的事务和锁

什么是事务 事务:是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作:这些操作作为一个整体一 起向系统提交,要么都执行.要么都不执行:事务是一组不可再分割的操作集合(工作逻辑单元): 事务的简单操作 显式启动事务语句,begin或者start transaction; 提交commit; 回滚rollback; SET AUTOCOMMIT=0 禁止自动提交 SET AUTOCOMMIT=1 开启自动提交 事务的四大特性(ACID) 原子性(Atomicity):事务一个事务(t

【MySQL】乐观锁和悲观锁

最近学习了一下数据库的悲观锁和乐观锁,根据自己的理解和网上参考资料总结如下: 悲观锁介绍(百科): 悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中, 将数据处于锁定状态.悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了 加锁机制,也无法保证外部系统不会修改数据). 使用场景举例:以MySQL InnoDB为例 商品goods表中有一

innodb事务锁

计算机程序锁 控制对共享资源进行并发访问 保护数据的完整性和一致性 lock  主要是事务,数据库逻辑内容,事务过程 latch/mutex 内存底层锁: 更新丢失 原因: B的更改还没有提交时,A已经再次修改了数据. 此时A使用原来的元数据作为基础更新后,B的更新便会丢失: 解决办法: 在修改数据上加写锁,当有锁时,A会等B更新提交完,才可以继续在B的基础上继续更新: 事务锁粒度 行锁: innodb ,oracle 页锁:sql server 表锁:Myisam ,memory 获取inno

MySQL行级锁、表级锁、页级锁详细介绍

原文链接:http://www.jb51.net/article/50047.htm 页级:引擎 BDB.表级:引擎 MyISAM , 理解为锁住整个表,可以同时读,写不行行级:引擎 INNODB , 单独的一行记录加锁 表级,直接锁定整张表,在你锁定期间,其它进程无法对该表进行写操作.如果你是写锁,则其它进程则读也不允许行级,,仅对指定的记录进行加锁,这样其它进程还是可以对同一个表中的其它记录进行操作.页级,表级锁速度快,但冲突多,行级冲突少,但速度慢.所以取了折衷的页级,一次锁定相邻的一组记

MySQL数据库事务隔离级别(Transaction Isolation Level)

今天在学习JDBC的时候看到了关于MySql的事务的隔离级别的问题,感觉内容挺高级的,所以记录一篇文章,以备后面使用. 数据库隔离级别有四种,应用<高性能mysql>一书中的说明: 然后说说修改事务隔离级别的方法: 1.全局修改,修改mysql.ini配置文件,在最后加上 1 #可选参数有:READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE. 2 [mysqld] 3 transaction-isolation = R

找出未提交的MySQL线程/事务

找出未提交的MySQL线程/事务: SELECT trx_id,trx_state,trx_started,trx_mysql_thread_id,CURRENT_TIMESTAMP - trx_started AS RUN_TIME from information_schema.innodb_trx; SELECT * from information_schema.processlist;   这个能看到上面哪个SQL线程ID(下图的378号线程就是造成MDL锁的罪魁祸首) SELECT

【MySQL】悲观锁&amp;乐观锁

悲观锁与乐观锁是两种常见的资源并发锁设计思路,也是并发编程中一个非常基础的概念.本文将对这两种常见的锁机制在数据库数据上的实现进行比较系统的介绍. 悲观锁(Pessimistic Lock) 悲观锁的特点是先获取锁,再进行业务操作,即"悲观"的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作.通常所说的"一锁二查三更新"即指的是使用悲观锁.通常来讲在数据库上的悲观锁需要数据库本身提供支持,即通过常用的select - for update操作来实现