MySQL数据库锁机制


所谓锁,为保证数据的一致性,对共享资源的在被并发访问变得有序的一种规则。

不同的MySQL存储引擎,有不同的锁机制或锁实现;总的来所,使用了三种锁级别,行级锁(row-level)、页级锁(page-level)、表级锁(table-level),依次锁定的资源粒度逐渐减小,锁资源是随着锁定资源粒度的减小,锁定同样数据需要的内存数量越来越多,算法也越来越负责,但同时应用程序遇到锁等待的可能也越来越底,系统的整体并发行随之提高;

表级锁,各大存储引擎粒度最大的锁级别,实现简单,获取锁和释放锁的速度快,也避免了死锁的问题,但同时带来了锁资源竞争的问题,导致并发度较底;表级锁分为读锁和写锁,MySQL通过四个队列来维护这两种锁定,两个存放当前正被锁定中的读和写的信息,两个存放等待读和写的信息,这四个队列分别为:read->lock\read_wait->lock->write->lock/write_wait->lock;读锁定,当前请求获取锁定的资源没有被写锁定,也没有在写锁定等待队列中有优先级更高写锁等待,立即进入read->lock,如果不满足,进入read_wait->lock;写锁定,当前请求写的资源没有被写锁定,并且没有在写锁定等待队列里面,那么再检测是否在读等待队列里面,如果有,进入写等待队列,如果没有,进入当前写队列;使用表级锁的通常是一些非事务性的存储引擎,如MyISAM、Memory、CSV等

页级锁,MySQL中毕竟特殊的一种锁级别,粒度介于行级锁和表级锁之间,获取和释放锁资源的负担也介于行级锁和表级锁之间,并发性同样也介于行级锁和表级锁之间,和行级锁一样,页级锁也可能发生死锁;主要是BerkeleyDB存储引擎是锁定方式;

行级锁,是RMDB实现的锁定粒度最小的锁,发生资源竞争的概率最小,能够给提供尽可能大的并发处理从而提供应用的性能。但同时由于颗粒度小,导致获取和释放锁需要的消耗也最大。另外,行级锁最容易发生死锁;行级锁定不是由MySQL实现的,而是由存储引擎实现的,如InnoDB和MySQL的分布式存储引擎NDBCluster等;InnoDB的行级锁同样分为两种,共享锁和排他锁,同样InnoDB也引入了意向锁(表级锁)的概念,所以也就有了意向共享锁和意向排他锁,所以InnoDB实际上有四种锁,即共享锁(S)、排他锁(X)、意向共享锁(IS)、意向排他锁(IX);

如果某些资源已经有了一个共享锁,那么在这些资源上面可以添加其他的共享锁,但不能添加排他锁;如果某些资源已经有了一个排他锁,那么在这些资源上不能添加其他的排他锁和共享锁,只能等待当前锁的释放,并获取锁资源后,才能对其加锁,但可以对其添加意向锁,即如果等待事务想要添加的是排他锁,那么可以在锁定行的所在表添加意向排他锁,如果等待事务想要添加的是共享锁,那么可以在锁定行所在表添加意向共享锁;InnoDB的锁实现与Oracle的锁实现有很大的不同,总的来说,Oracle锁定数据是根据某行记录所在的物理block上的事务槽上表级锁定信息,而InnoDB的锁定则是通过指向数据记录的第一条索引之前和最后一条索引之后的空域空间上标记锁信息来实现的,所以InnoDB的这种锁实现有被称为“Next Key Locking”(间隙锁),间隙锁一个比较大的弱点是,当锁定一定范围的键值后,即使一些不存在的键值也回被无辜的锁定,导致这种键值的记录不能被insert。当然,这种情况只会出现在InnoDB的默认的事务隔离级别repeatable-read才会出现,如果降低InnoDB的事务隔离级别为read commited则不会出现这样的情况。InnoDB给出的解释是间隙锁可以阻止幻读的出现,但其实间隙锁只能阻止部分幻读的情况,但不能阻止全部。通过索引来实现锁的方式还有一个更大的隐患是,当Query不能使用索引时,行级锁将会上升为表级锁,会将整张数据表锁住,造成并发性能的降低。

死锁,行级锁可能产生死锁,InnoDB也不例外。InnoDB有一套检测死锁的机制,但前提是死锁的场景涉及的存储引擎都是InnoDB的时候。如果InnoDB检测到死锁的存在,那么就将影响数据行数最小的个事务回滚。

那么有什么办法来避免InnoDB的间隙锁带来的麻烦吗?有三种办法:1、降低并发,避免出现资源竞争,但这样会在一定程度上降低应用的性能;2、修改InnoDB的默认的事务隔离级别,由repeatable-read修改为read commited,当然修改事务隔离级别带来的另外一个隐患就是可能会出现不可重复读;3、在查询数据是,一定要使用索引,避免全表扫描,在insert数据时,使用增长的索引字段(即每次插入的索引字段的值一定保证是增长的)。

相关内容