MySQL锁机制
Posted EileenChang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL锁机制相关的知识,希望对你有一定的参考价值。
文章目录
概述
锁是计算机协调多个进程或线程并发访问某一资源的机制。在应用程序中,数据是多个用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。在mysql中,锁和存储引擎是强相关的,不同的存储引擎有不同的锁机制。
1 锁的粒度
从锁的粒度来说,mysql的锁分为行锁、页面锁和表锁。不同的存储引擎支持不同粒度的锁。
- 表锁:开销小,加锁快,不会产生死锁;但是锁粒度大,发生锁冲突的概率高,并发度低。
- 行锁:开销大,加锁慢,会产生死锁;优点是锁粒度小,发生冲突的概率低,并发度高。
- 页面锁:开销和加锁时间介于表锁和行锁之间,也会产生死锁;锁的粒度介于表锁和行锁之间,并发度一般。
InnoDB存储引擎既支持行锁,也支持表锁,它默认情况下是使用行锁。MyISAM和MEMORY存储引擎使用的是表锁。BDB存储引擎默认使用页面锁,同时也支持表锁。
2 锁的实现
2.1 MyISAM锁的实现
2.1.1 MySQL表锁
MySQL的表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;对 MyISAM表的写操作,则会阻塞其他用户对同一表的读和写操作;MyISAM表的读操作与写操作之间,以及写操作之间是串行的!
默认情况下,写锁比读锁具有更高的优先级。当一个锁释放时,这个锁会优先分配给写锁等待队列中的请求,然后再分配给读锁等待队列中的请求。这也正是MyISAM表不太适合于有大量更新操作和查询操作应用的原因,因为,大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。同时,一些慢查询会导致写操作阻塞,应用中应尽量避免出现慢查询操作。
2.1.2 MyISAM加锁方式
- 自动加锁:MyISAM在执行查询语句前,会自动给涉及的表加共享读锁,在执行更新操作前,会自动给涉及的表加独占写锁,这个过程并不需要用户干预。
- 显式加锁,除了自动加锁以外,用户还可以直接用LOCK TABLE命令给MyISAM 表显式加锁。
2.1.3 写阻塞读
Session A | Session B |
---|---|
对表student加独占写锁: lock table student write; | |
Session A对student表的读写操作均可执行: select * from student; update student set name = ‘John’ where id = 1; | Sesson B对student表的读操作被阻塞: select * from student; |
Session A释放锁: unlock tables; | Session B被阻塞的查询可以继续执行并返回结果 |
2.1.4 读阻塞写
Session A | Session B |
---|---|
对student表加共享读锁: lock table student read; | |
Session A可以读取student表: select * from student; | Session B可以读取student表: select * from student; |
Session A不能查询没有锁定的表: select * from person; 报错:Table ‘person’ was not locked with LOCK TABLES | Session B可以查询或者更新未锁定的表 select * from person; insert into person values(1, ‘Tom’); |
Session A不能插入或者更新student表: insert into student values(6, ‘Sunny’); 报错:Table ‘student’ was locked with a READ lock and can’t be updated | Session B插入student表会阻塞: insert into student values(6, ‘Kaly’); |
2.2 InnoDB锁的实现
2.2.1 InnoDB行锁
- 共享锁(S):又称读锁,允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
- 排他锁(X):又称写锁,允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享锁和排他锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。
2.2.2 行锁加锁方式
- 对于更新语句,InnoDB引会自动给涉及到的数据加上排他锁。
- select语句默认不会加任何锁类型。
- 如果查询语句要加共享锁,可以使用select … lock in share mode语句,其他事务仍然可以查询记录(普通的select语句),并也可以对该记录加共享锁。但是如果当前事务需要对该记录进行更新操作,则很有可能造成死锁。共享锁可以确保当前事务查到的数据是最新的数据,并且不允许其他事务来修改数据。但是自己不一定能够修改数据,因为有可能其他的事务也对这些数据加了S锁。
- 如果查询语句要加排他锁,可以使用select … for update语句,加共享锁可以使用select … lock in share mode语句。其他事务可以查询该记录(普通的select语句),但是不能对该记录加共享锁或排他锁,而是等待释放锁。排他锁确保当前事务查到的数据是最新数据,并且查到后的数据只允许自己来修改。
2.2.3 行锁实现方式
- InnoDB 行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB 这种行锁实现特点意味着,只有通过索引条件检索数据,InnoDB 才使用行级锁,否则将退化为表锁。
- 不论是使用主键索引、唯一索引或普通索引,InnoDB 都会使用行锁来对数据加锁。
- 只有执行计划真正使用了索引,才能使用行锁。即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的。如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。
- 由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以即使多个事务访问不同行的记录,但是如果是使用相同的索引键,还是会出现锁冲突的。
2.2.4 意向锁
为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁:
- 意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
- 意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
2.2.5 间隙锁(Gap Locks)
间隙锁是指在某个索引项的两个索引值之间的数据集(where条件为between and)加的锁,或者是对某个索引值之前的数据集(where条件为<)或某个索引值之后的数据集(where条件为>)加的锁。比如,SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE,将会阻止其他事务将数据插入到c1列的10到20的范围内。
2.2.6 临键锁(Next-Key Locks)
临键锁是行锁(Record Locks)以及“where条件为<”的间隙锁的结合。
2.2.7 自增锁(AUTO-INC Locks)
自增锁是一种特殊的表锁,当一个事务使用自增属性插入表数据时,会使用自增锁。
3 死锁
3.1 MyISAM死锁
在自动加锁的情况下,MyISAM总是一次获得SQL语句所需要的全部锁,所以MyISAM不会出现死锁。
3.2 InnoDB死锁
避免死锁:
- 为了在单个InnoDB表上执行多个并发写入操作时避免死锁,可以在事务开始时一次性获取所有需要修改的记录的锁。
- 在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁、更新时再申请排他锁,因为这时候当用户再申请排他锁时,其他事务可能又已经获得了相同记录的共享锁,从而造成锁冲突,甚至死锁。
- 如果事务需要修改或锁定多个表,则应在每个事务中以相同的顺序使用加锁语句。 在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会。
如果出现死锁,可以用 SHOW INNODB STATUS 命令来确定最后一个死锁产生的原因。返回结果中包括死锁相关事务的详细信息,如引发死锁的 SQL 语句,事务已经获得的锁,正在等待什么锁,以及被回滚的事务等。据此可以分析死锁产生的原因和改进措施。
4 锁监控
4.1 表锁监控
MyISAM或InnoDB可以通过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁定争夺,如果Table_locks_waited值很大,意味着存在比较严重的表锁争用情况。
mysql> show status like '%table_locks%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Table_locks_immediate | 100 |
| Table_locks_waited | 0 |
+-----------------------+-------+
2 rows in set (0.00 sec)
4.2 行锁监控
InnoDB可以通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况,如果InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比较高,说明行锁争用激烈。
mysql> show status like '%innodb_row_lock%';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0 |
| Innodb_row_lock_time | 0 |
| Innodb_row_lock_time_avg | 0 |
| Innodb_row_lock_time_max | 0 |
| Innodb_row_lock_waits | 0 |
+-------------------------------+-------+
5 锁优化
- 尽量使用较低的隔离级别。
- 精心设计索引,并尽量使用索引访问数据,使加锁更精确,从而减少锁冲突的机会。
- 选择合理的事务大小,小事务发生锁冲突的几率也更小。
- 给记录集显式加锁时,最好一次性请求足够级别的锁。比如要修改数据的话,最好直接申请排他锁,而不是先申请共享锁,修改时再请求排他锁,这样容易产生死锁。
- 不同的程序访问一组表时,应尽量约定以相同的顺序访问各表,对一个表而言,尽可能以固定的顺序存取表中的行,这样可以大大减少死锁的机会。
- 尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响。
- 不要申请超过实际需要的锁级别。
- 除非必须,查询时不要显示加锁, MySQL的MVCC了实现普通查询不用加锁。
以上是关于MySQL锁机制的主要内容,如果未能解决你的问题,请参考以下文章