MySQL锁定机制简介

为了保证数据的一致完整性,任何一个数据库都存在锁定机制。锁定机制的优劣直接应想到一个数据库系统的并发处理能力和性能,所以锁定机制的实现也就成为了各种数据库的核心技术之一。本章将对MySQL中两种使用最为频繁的存储引擎MyISAM和Innodb各自的锁定机制进行较为详细的分析。

MySQL锁定机制简介

总的来说,MySQL各存储引擎使用了三种类型(级别)的锁定机制:行级锁定页级锁定表级锁定。下面我们先分析一下MySQL这三种锁定的特点和各自的优劣所在。

行级锁

行级锁定是目前各大数据库管理软件所实现的锁定颗粒度最小的,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。  但是由于锁定资源的颗粒度很小,所以每次获取锁和释放锁消耗的资源也更多,带来的消耗自然也就更大了。此外,行级锁定也最容易发生死锁

表级锁

表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免死锁问题。  当然,锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的概率也会最高,致使并大度较低

页级锁

页级锁定的特点是锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力也同样是介于上面二者之间。另外,页级锁定和行级锁定一样,会发生死锁。 在MySQL数据库中,使用表级锁定的主要是MyISAM,Memory,CSV等一些非事务性存储引擎,而使用行级锁定的主要是Innodb存储引擎和NDBCluster存储引擎,页级锁定主要是BerkeleyDB存储引擎的锁定方式。

表级锁定

MySQL的表级锁定主要分为两种类型,一种是读锁定,另一种是写锁定  在MySQL中,主要通过四个队列来维护这两种锁定:两个存放当前正在锁定中的读和写锁定信息,另外两个存放等待中的读写锁定信息,如下:

  • Current read-lock queue (lock->read) 持有读锁的所有线程
  • Pending read-lock queue (lock->read_wait) 等待读锁的所有线程
  • Current write-lock queue (lock->write) 持有写锁的所有线程
  • Pending write-lock queue (lock->write_wait) 等待写锁的所有线程

当前持有读锁的所有线程的相关信息都能够在Currentread-lockqueue中找到,队列中的信息按照获取到锁的时间依序存放。而正在等待锁定资源的信息则存放在Pendingread-lockqueue里面,另外两个存放写锁信息的队列也按照上面相同规则来存放信息。

读锁定

一个新的客户端请求在申请获取读锁定资源的时候,需要满足两个条件:

  1. 资源没有被写锁定
  2. 写锁定等待队列(Pendingwrite-lockqueue)中没有更高优先级的写锁定等待

如果满足了上面两个条件之后,该请求会被立即通过,并将相关的信息存入Currentread-lockqueue中,否则会被迫进入等待队列Pendingread-lockqueue中等待资源的释放。

写锁定

一个新的客户端请求在申请获取写锁定资源的时候,被申请资源需要满足两个条件:

  1. 没被写锁定Currentwrite-lockqueue
  2. 没被写锁定等待(Pendingwrite-lockqueue)
  3. 没被读锁定Currentread-lockqueue

行级锁定

行级锁定不是MySQL自己实现的锁定方式,而是由其他存储引擎自己所实现的,如广为大家所知的Innodb存储引擎,以及MySQL的分布式存储引擎NDBCluster等都是实现了行级锁定。

Innodb锁定模式及其实现机制

总的来说,Innodb的锁定机制和Oracle数据库有不少相似之处。Innodb的行级锁定同样分为两种类型,共享锁排他锁,而在锁定机制的实现过程中为了让行级锁定和表级锁定共存,Innodb也同样使用了意向锁(表级锁定)的概念,也就有了意向共享锁意向排他锁这两种。

当对某个资源加锁时,如果 

  • 有共享锁,可以再加一个共享锁,不过不能加排他锁。 
  • 有排它锁,就在表上添加意向共享锁或意向排他锁。  意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在。所以,可以说Innodb的锁定模式实际上可以分为四种:共享锁(S),排他锁(X),意向共享锁(IS)和意向排他锁(IX)

Innodb与Mysql锁实现机制的区别

  • Oracle锁定数据是通过需要锁定的某行记录所在的物理block上的事务槽上表级锁定信息
  • Innodb的锁定则是通过在指向数据记录的第一个索引键之前和最后一个索引键之后的空域空间上标记锁定信息而实现的。

Innodb的这种锁定实现方式被称为“NEXT-KEYlocking”(间隙锁),因为Query执行过程中通过过范围查找的华,他会锁定整个范围内所有的索引键值,即使这个键值并不存在。

间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害。而Innodb给出的解释是为了组织幻读的出现,所以他们选择的间隙锁来实现锁定。

除了间隙锁给Innodb带来性能的负面影响之外,通过索引实现锁定的方式还存在其他几个较大的性能隐患

  • 当Query无法利用索引的时候,会放弃行级别锁定而改用表级别的锁定
  • 当Quuery使用的索引并不包含所有过滤条件的时候,间隙锁会锁定不包含的记录,而不是具体的索引键
  • 当Query在使用索引定位数据的时候,如果使用的索引键一样但访问的数据行不同的时候(索引只是过滤条件的一部分),一样会被锁定

Innodb事务隔离级别下的锁定及死锁

在Innodb的事务管理和锁定机制中,有专门检测死锁的机制,会在系统中产生死锁之后的很短时间内就检测到该死锁的存在。

当Innodb检测到系统中产生了死锁之后,Innodb会通过相应的判断来选这产生死锁的两个事务中较小的事务来回滚,而让另外一个较大的事务成功完成。

但是有一点需要注意的就是,当产生死锁的场景中涉及到不止Innodb存储引擎的时候,Innodb是没办法检测到该死锁的,这时候就只能通过锁定超时限制来解决该死锁了。另外,死锁的产生过程的示例将在本节最后的Innodb锁定示例中演示。

Innodb锁定机制示例

下面我们以InnodDB引擎来说明产生死锁的情况

行锁定演示

Session1 Session2
set autocommit=0 set autocommit=0
update test set b = ‘b1’ where a = 1; 更新但不提交
update test set b = ‘b1’ where a = 1; 被阻塞,等待
commit; 提交
update test set b = ‘b1’ where a = 1; 解除阻塞

无索引升级为表锁定

Session1 Session2
update test set b = ‘2’ where b = 2000; update test set b = ‘3’ where b = 3000; 阻塞,等待
commit;
update test set b = ‘3’ where b = 3000;解除阻塞,完成更新

间隙锁带来的插入问题

Session1 Session2
select from test; update test set b = a 100 where a < 4 and a > 1;
mysql> insert into test values(2,’200’);被阻塞,等待
commit;
insert into test values(2,’200’);Query OK, 1 row affected (38.68 sec)

死锁示例

Session1 Session2
update t1 set id = 110 where id = 11;
update t2 set id = 210 where id = 21;
update t2 set id=2100 where id=21;阻塞,等待session2释放
update t1 set id=1100 where id=11; 阻塞,等待session1释放
两个 session 互相等等待对方的资源释放之后才能释放自己的资源,造成了死锁

合理利用锁机制优化MySql

MyISAM表锁优化建议

在优化MyISAM存储引擎锁定问题的时候,最关键的就是如何让其提高并发度。由于锁定级别是不可能改变的了,所以我们首先需要尽可能让锁定的时间变短,然后就是让可能并发进行的操作尽可能的并发。

  1. 缩短锁定时间

尽两减少大Query,将复杂Query分拆成小的Query分布进行;  尽可能的建立足够高效的索引,让数据检索更迅速;  尽量让MyISAM存储引擎的表只存放必要的信息,控制字段类型;  利用合适的机会优化MyISAM表数据文件;

2、分离能并行的操作

可能有些人会认为在MyISAM存储引擎的表上读写锁就只能是完全的串行化,没办法再并行了。大家不要忘记了,MyISAM的存储引擎还有一个非常有用的特性,那就是ConcurrentInsert(并发插入)的特性。  Concurrent_insert=2,无论MyISAM存储引擎的表数据文件的中间部分是否存在因为删除数据而留下的空闲空间,都允许在数据文件尾部进行  ConcurrentInsert;concurrent_insert=1,当MyISAM存储引擎表数据文件中间不存在空闲空间的时候,可以从文件尾部进行ConcurrentInsert;  concurrent_insert=0,无论MyISAM存储引擎的表数据文件的中间部分是否存在因为删除数据而留下的空闲空间,都不允许ConcurrentInsert。

3、合理利用读写优先级

表级锁定对于读和写是有不同优先级设定的,默认情况下是写优先级要大于读优先级。所以,如果我们可以根据各自系统环境的差异决定读与写的优先级。如果我们的系统是一个以读为主,而且要优先保证查询性能的话,我们可以通过设置系统参数选项low_priority_updates=1,将写的优先级设置为比读的优先级低,即可让告诉MySQL尽量先处理读请求。

Innodb行锁优化建议

*尽可能让所有的数据检索都通过索引来完成,从而避免Innodb因为无法通过索引键加锁而升级为表级锁定;

  • 合理设计索引,让Innodb在索引键上面加锁尽可能准确,尽可能的缩小锁定范围,避免造成不必要的锁定而影响其他Query的执行;
  • 尽可能减少基于范围的数据检索过滤条件,避免间隙锁带来的负面影响而锁定了不该锁定的记录;
  • 尽量控制事务的大小,减少锁定的资源量和锁定时间长度;
  • 在业务环境允许的情况下,尽量使用较低级别的事务隔离,以减少MySQL因为实现事务隔离级别所带来的附加成本;

由于Innodb的行级锁定和事务性,所以肯定会产生死锁,下面是一些比较常用的减少死锁产生概率

a. 类似业务模块中,尽可能按照相同的访问顺序来访问,防止产生死锁;  b. 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;  c. 对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;

系统锁定争用情况查询对于两种锁定级别,MySQL内部有两组专门的状态变量记录系统内部锁资源争用情况 MySQL 实现的表级锁定的争用状态变量: Table_locks_immediate:产生表级锁定的次数; Table_locks_waited:出现表级锁定争用而发生等待的次数;

对于Innodb所使用的行级锁定,系统中是通过另外一组更为详细的状态变量来记录的,如下: Innodb 的行级锁定状态变量不仅记录了锁定等待次数,还记录了锁定总时长,每次平均时长,以及最大时长,此外还有一个非累积状态量显示了当前正在等待锁定的等待数量。对各个状态量的说明如下:

  • Innodb_row_lock_current_waits:当前正在等待锁定的数量;
  • Innodb_row_lock_time:从系统启动到现在锁定总时间长度;
  • Innodb_row_lock_time_avg:每次等待所花平均时间;
  • Innodb_row_lock_time_max:从系统启动到现在等待最常的一次所花的时间;
  • Innodb_row_lock_waits:系统启动后到现在总共等待的次数;

对于这5个状态变量,比较重要的主要是Innodb_row_lock_time_avg(等待平均时长),Innodb_row_lock_waits(等待总次数)以及Innodb_row_lock_time(等待总时长)这三项。尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手指定优化计划。

此外,Innodb除了提供这五个系统状态变量之外,还提供的其他更为丰富的即时状态信息供我们分析使用。可以通过如下方法查看

1.通过创建InnodbMonitor表来打开Innodb的monitor功能:  2.然后通过使用“show engine innodb status;”查看细节信息(由于输出内容太多就不在此记录了)

联系我们

邮箱 626512443@qq.com
电话 18611320371(微信)
QQ群 235681453

Copyright © 2015-2024

备案号:京ICP备15003423号-3