MySQL的锁机制

  1、在处理并发读写时,通常使用一套锁系统来解决问题。锁系统由读锁(又称共享锁)和写锁(又称排他锁)组成。

  2、每一种锁操作(如获得锁、检查锁是否已解除、释放锁),都会增加系统开销。锁策略就是在锁开销和数据安全之间寻求一种平衡。MySQL的每种存储引擎都可以实现独有的锁策略或锁粒度。在存储引擎设计中,锁管理是个非常重要的议题。两种最重要的锁策略:

  1)行级锁:可以支持最大的并发处理能力,但也带来最大的锁开销。行级锁由存储引擎(如InnoDB)实现,服务器完全不了解存储引擎里的锁实现方式。

  2)表级锁:开销最小。

  由于通常认为表更新比表检索更重要,因此写锁比读锁有更高的优先级。

  虽然存储引擎管理自己的锁,但MySQL本身也能使用各种有效的表级锁,如MySQL服务器在ALTER TABLE语句中使用表级锁,而不用考虑存储引擎。

  以下内容仅用于讨论MyISAM表,如下图(对于InnoDB表,READ和READ LOCAL表级锁是一样的,均不支持下文的并发插入

  

  使用表级锁的时候,读写一般是串行的。但使用READ LOCAL表级锁可以支持某种类型的并发写操作,如下面的图文说明:

  (1)此时concurrent_insert变量*为1,且表没有出现空洞(自表创建以来,还没有删除操作)。如图,在session 1中对表使用read local表级锁,在session 2中可以执行插入操作,但session 1要unlock tables之后才能看到session 2刚刚插入的数据。图中序号标识操作的顺序。

  

  以下三种情况可以用来作为对比,图略:

  I、若session 1使用read local表级锁,session 2执行update操作,则会被阻塞。

  II、若session 1只使用read表级锁,自然地,session 2插入数据时会阻塞,直到读锁释放。

  III、若session 1获得读锁(read和read local),则它再进行插入或更新操作时都会报错。

  (2)此时concurrent_insert变量为1。删除id=4的记录,表出现了空洞。如图,在session 1中对表使用read local表级锁,则session 2的插入操作会阻塞。

  

  此时可以使用optimize table来进行碎片整理。如图,使用该命令之后,上述的并发插入操作不再阻塞。

    

  (3)还是上面的表(使用optimize table之前的有空洞的表),但设置concurrent_insert为2。如图,session 1对表使用了read local表级锁,session 2可以执行插入操作。

  

  (4)总结:

  read localread都是表级读锁,但前者允许在一定条件下,对读锁定的表执行并发插入操作。

  MyISAM的删除操作不会重新安排整个表,而只是把行标记为已删除(表的MYD文件还是同样大小),这在表中留下了“空洞”。删除的行会在一个链表中维护。MyISAM在可能的情况下会优先使用这些“空洞”,为插入复用空间。如果表是完整的(没有“空洞”),则把新行插入到表尾。

  concurrent_insert全局变量主要用于控制并发插入行为:

  当它为0时,不允许并发插入(与实际测试不符?);

  当它为1(默认值)时,若表不存在“空洞”,则允许并发插入;否则,不允许并发插入;

  当它为2时,即使表存在“空洞”,也允许并发插入。该值在MySQL5.0及更高的版本可用。

  为什么表存在“空洞”与否对并发插入有影响呢?这是因为(参照下图):

  表中不存在“空洞”时,数据会插入到图中最下方区域,此时无论concurrent_insert为1还是2,插入操作都不会和锁构成“竞争”关系,因此不会阻塞;表存在“空洞”时,由于系统要重用“空洞”区域,所以区分了以下情形:concurrent_insert为1时,认为是要重用“空洞”区域,但由于被锁定,所以插入操作阻塞;concurrent_insert为2时,认为先暂时不用“空洞”区域,而是插入到图中最下方区域,和锁不构成“竞争”关系,因而插入操作不会阻塞。

  optimize table碎片整理之后,图中“空洞”区域被重用,即不再存在“空洞”,因此concurrent_insert为1时,也可以执行并发插入操作了。

  另外,update操作由于是更新read local锁定的区域内的记录,和锁构成“竞争”关系,所以会阻塞。

  

  

 

  注释*

  concurrent_insert全局变量(某些实现中它的值存储在information_schema.GLOBAL_VARIABLES中)可以通过两种方式取得:在mysql命令行中执行 show global variables like '%concurrent_insert%';,或者在shell命令行中执行mysqladmin -u username -p passwd variables |grep concurrent_insert;另外,可以通过set global concurrent_insert = 0/1/2设置该变量。

 

  参考资料:

  《高性能MySQL》

 

 

 

 

 

不断学习中。。。

posted on 2015-01-19 20:51  han'er  阅读(1802)  评论(0编辑  收藏  举报

导航