MySQL行锁深入研究 这篇文章探讨了在某些情况下,行锁会变成表锁的情况。在本文中,我们会通过工具来判断出语句到底使用了表锁,还是行锁。
首先我们先来简单了解下概念,以下讨论只是针对innodb引擎的。
锁的基本概念
mysql支持行锁和表锁。有两种锁的类型:
shared (S) lock :共享锁,允许事务获取锁来读取一行;
exclusive (X) lock: 排它锁,允许事务获取锁来更新或删除一行。
应用场景
在很多情况下,我们会通过mysql中的锁机制来支持高并发。比如有两个会话(链接)更新同一数据,我们可以在会话一上加锁,那么会话二会阻塞等待,直到会话一提交完成,释放相应的锁。
在这种情况下,我们可以使用SELECT … LOCK IN SHARE_MODE和SELECT … FOR UPDATE语句。
SELECT … LOCK IN SHARE_MODE: 对受影响的行加上共享锁,其它对话也可以查询出结果,但是对加锁的行不能更新或删除,直到获得共享锁会话提交。
SELECT … FOR UPDATE : 对受影响的行加上排它锁,如果其它会话执行SELECT … FOR UPDATE 此语句,则会阻塞,不会出现查询结果,直到获得排它锁的会话提交。
实际操作
我们来实际看下前文提到的文章中的例子。
在第一个session里运行:
BEGIN;
SELECT * FROM book WHERE num=11 FOR UPDATE; |
出现结果:
| id | num | name| | 11 | 11 | NULL | | 12 | 11 | NULL | | 13 | 11 | NULL | | 14 | 11 | NULL | | 15 | 11 | NULL | 5 rows in set |
这时,我们通过运行(老版本应该使用show innodb status):
1 |
show engine innodb status\G |
我们看到11行被加上了锁。例子中的数据只有10行(不清楚这里的多的1行从哪里来的)。但按我们自己的理解,应该是被锁住5行而已,那么看来,session 1 对表book所有行都加了锁。
然后在第二个session里运行:
BEGIN;
SELECT * FROM book WHERE num=22 FOR UPDATE; |
出现结果:
| id| num | name | | 16 | 22 | NULL | | 17 | 22 | NULL | | 18 | 22 | NULL | | 19 | 22 | NULL | | 20 | 22 | NULL | 5 rows in set |
在这里,我们可以看下,在session 2 中出现了查询结果。这与我们的理解又不一样了,因为select…for update 加的应该是排它锁,session 2 应该会阻塞,但是却能查出结果,说明,session 1 不仅是表锁,而且是共享锁。那么同理,session 2 也是表锁,并且也是共享锁。
再使用show engine innodb status看下:
和我们的理解一致了,按照作者的思路再往下看。
回到第一个session,运行:
UPDATE book SET name=’abc’ WHERE num=11; |
在这里,问题来了,session 1 处在了等待状态,这怎么理解呢?
其实,上面的分析之后,很好理解了。我们现在知道,session 1和session 2 都是表锁,并且都是共享锁。那么执行update 时,需要的是排它锁。那就变成了session 1需要获得排它锁,然而session 2已经获得了共享锁。所以,session 1 只能等待。同理,如果session 2 执行update 也会等待,因为session 1 已经获得了共享锁。
那么,在文章中的例子,就是一种死锁的情况。在这种情况下,只读是没有问题的,但一旦要更新,就会死锁了。所以,在使用行锁的时候,不要变成表锁。我们可以通过show engine innodb status查看锁的状态。
那什么情况会导致表锁
(1)在不通过索引条件查询的时候,InnoDB使用的是表锁,而不是行锁
(2)由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。应用设计的时候要注意这一点。
(3) 当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。
(4)即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。
参考:http://brilon.iteye.com/blog/433726