MySQL性能调优与架构设计(六)—— MySQL数据库锁定机制

前言

  1. 在说锁定机制以前,有必要理解下并发与并行的基本概念。
  2. 并发是指一台处理器上同时处理多个任务,并行是指多个处理器同时处理多个任务,如hadoop分布式集群。
  3. 通俗的讲,并发就是不一样线程同时干一件事情,并行就是不一样线程同时干不一样的事情。
  4. 因此并发编程的目标是充分的利用处理器的每个核,以达到最高的性能。
  5. 那么并发必然会牵扯到应用系统中资源的竞争。
  6. 详细的关于并行与并发能够参考一篇文章来理解:https://blog.csdn.net/qq_3329...
  7. 在高并发的状况下,为了保证数据的完整一致性,任何一个数据库都有锁定机制。
  8. 锁定机制的优劣直接影响了一个数据库的并发处理能力和性能。
  9. 本章将对mysql中两种使用最为频繁的存储引擎MyISAM和InnoDB各自的锁定机制进行较为详细的分析。

MySQL锁定机制简介

  1. 数据库锁定机制简单来讲就是数据库为了保证数据的一致性而使各类共享资源在被并发访问变得有序所设计的一种规则。
  2. 对于任何一种数据库来讲都须要有相应的锁定机制,因此MySQL天然也不例外。
  3. MySQL数据库因为其自身架构的特色,存在多种数据存储引擎,每种存储引擎所针对的应用场景特色都不太同样,为了知足各自特定应用场景的需求,每种存储引擎的锁定机制都是为各自所面对的特定场景而优化设计,因此,各类存储引擎的锁定机制也有较大区别。
  4. 总的来讲,MySQL各存储引擎使用了三种类型(级别)的锁定机制:行级锁定,页级锁定和表级锁定。

行级锁定(row-level)

  1. 行级锁,通常是指排它锁,即被锁定行不可进行修改、删除,只能够被其余会话select。
  2. 排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其余锁并存,如一个事务获取了一个数据行的排他锁,其余事务就不能再获取该行的其余锁。
  3. 行级锁定最大的特色就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。
  4. 因为锁定颗粒度很小,因此发生锁定资源竞争的几率也小,可以给予应用程序尽量大的并发处理能力提升一些须要高并发应用系统的总体性能。
  5. 虽然在并发处理能力上面有较大的优点,可是行级锁定也所以带来了很多弊端。
  6. 因为锁定资源的颗粒度很小,因此每次获取锁和释放锁须要作的事情也不少,带来的消耗天然也就更大了。
  7. 此外,行级锁定也最容易发生死锁。

表级锁定(table-level)

  1. 表级锁,直接锁定整张表,在你锁按期间,其余进程没法对该表进行写操做。若是你是写锁,则其余进程则读也不容许。
  2. 和行级锁定相反,表级别的锁定是mysql各存储引擎中最大颗粒度的锁定机制。
  3. 该锁定机制最大的特色就是实现逻辑很是简单,带来的系统负面影响最小。因此获取锁和释放锁的速度很快。
  4. 因为表级锁一次会将整个表锁定,因此能够很好的避免困扰咱们的死锁问题。
  5. 固然,锁定颗粒度大带来的负面影响就是出现资源争用的几率也会很高,导致并发度大打折扣。

页级锁定(page-level)

  1. 页级锁定是MySQL中比较独特的一种锁定级别,在其余数据库管理软件中也并非太常见。
  2. 页级锁定的特色是锁定颗粒度介于行级锁定和表级锁定之间,因此获取锁所须要的资源开销,以及所能提供的并发处理能力也一样介于上面两者之间。
  3. 另外,页级锁定和行级锁定同样,也会发生死锁。

小结

  1. 在数据库实现资源锁定的过程当中,随着锁定资源颗粒度的减少,锁定相同数据量的数据所须要消耗的内存数量是愈来愈多,实现算法也会愈来愈复杂。
  2. 随着锁定资源颗粒度的减少,应用程序的访问请求遇到锁等待的可能性也会随之下降,系统总体并发度页随之提高。
  3. 在MySQL中,使用表级锁定的是MyISAM、MEmory、CSv等一些非事务型存储引擎,而使用行级锁的主要是InnoDB存储引擎和NDB Cluster存储引擎,页级锁定主要是BerkeleyDB存储引擎的锁定方式。

各类锁定机制分析

表级锁定

  1. MySQL的表级锁定主要分为两种类型,一种是写锁定,一种是读锁定。
  2. 在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)
  3. 当前持有读锁的全部线程的相关信息都可以在Current read-lock queue (lock->read) 中找到,队列中的信息按照获取到锁的时间依次存放。而正在等待读锁的信息则存放在Pending read-lock queue 里面,另外两个存放写锁信息的队列也按照相同的规则来存放信息。

表级读、写锁定

  1. 读锁定:html

    (1)一个新的客户端请求在申请读锁资源的时候,须要知足两个条件:
        【1】请求锁定的资源没有写锁定
        【2】写锁定等待队列Pending write-lock queue中没有更高优先级的写锁定等待
    (2)若是知足来上述两个条件以后,该请求会当即经过,并将相关的信息存入Current read-lock queue 中,而若是上面两个条件中有一个不知足,都会被迫进入等待队列Pending read-lock queue中等待资源的释放。
  2. 写锁定:mysql

    (1)当客户端请求写锁定的时候,MySQL首先会检查Current write-lock queue中是否已经有锁定相同资源的信息存在。
    (2)若是Current write-lock queue中没有,则再检查Pending write-lock queue;
    (3)若是Pending write-lock queue中找到了,本身也须要进入等待队列并暂停自身线程等待锁定资源。
    (4)反之,若是Pending write-lock queue为空,则再检测Current read-lock queue,若是有锁定存在,则一样须要进入Pending write-lock queue等待。
    (5)有两种特殊状况,会当即得到锁而进入Current write-lock queue中:
        【1】请求锁定的类型为WRITE_DELAYED;
        【2】请求锁定类型为WRITE_CONCURRENT_INSERT或者TL_WRITE_ALLOW_WRITE,同时Current read lock是READ_NO_INSERT的锁定类型。 
    (6)若是一开始就检测到Current write-lock queue中已经存在了锁定相同资源的写锁定存在,那么就只能进入等待队列等待相应资源锁定的释放。
  3. 虽然对于咱们这些使用者来讲,mysql展示出来的只有读锁和写锁,实际上在MySQL内部实现中却有多达11种锁定类型,有系统中一个枚举量(thr_lock_type)定义。具体的类型能够另外查询。
  4. 读请求和写等待队列中的写锁请求的优先级规则主要为如下规则决定:算法

    (1)除了READ_HIGH_PRIORITY的读锁定以外,Pending write-lock queue中的    WRITE写锁定可以阻塞全部其余的读锁定;
    (2)READ_HIGH_PRIORITY读锁定的请求可以阻塞全部Pending write-lock queue中的写锁定;
    (3)除了WRITE写锁定以外,Pending write-lock queue中的其余任何写锁定都比读锁定的优先级低。
  5. 写锁定出如今Current write-lock queue以后,会阻塞除了如下状况下的全部其余锁定的请求:sql

    (1)在某些存储引擎的容许下,能够容许一个WRITE_CONCURRENT_INSERT写锁定请求 
    (2)写锁定为WRITE_ALLOW_WRITE的时候,容许除了WRITE_ONLY以外的全部读和写锁定请求 
    (3)写锁定为WRITE_ALLOW_READ的时候,容许除了READ_NO_INSERT以外的全部读锁定请求 
    (4)写锁定为WRITE_DELAYED 的时候,容许除了READ_NO_INSERT以外的全部读锁定请求 
    (5)写锁定为WRITE_CONCURRENT_INSERT的时候,容许除了READ_NO_INSERT以外的全部读锁定请求

行级锁定- Innodb锁定模式及实现机制

  1. Innodb的行级锁定分为两种类型,共享锁和排他锁。
  2. 在锁定机制的实现过程当中为了让行级锁定和表级锁定共存,Innodb也一样使用了意向锁(表级锁定)的概念,也就有了意向共享锁和意向排他锁这两种。
  3. 当一个事务须要给本身须要的某个资源加锁的时候,若是遇到一个共享锁正锁定着本身须要的资源,本身能够再加一个共享锁,不过不能加排他锁。
  4. 可是,若是遇到本身须要锁定的资源已经被一个排他锁占有以后,则只能等待该锁定释放资源以后本身才能获取锁定资源并添加本身的锁定。
  5. 而意向锁的做用就是当一个事务在须要获取资源锁定的时候,若是遇到本身须要的资源已经被排他锁占用的时候,该事务能够须要锁定行的表上面添加一个合适的意向锁。
  6. 若是本身须要一个共享锁,那么就在表上面添加一个意向共享锁。
  7. 而若是本身须要的是某行上面添加一个排他锁的花话,则先在表上面添加一个意向排他锁。
  8. 意向共享锁能够同时并存多个,可是意向排他锁同时只能有一个存在。
  9. 因此,能够说Innodb的锁定模式实际上能够分为四种:共享锁(S),排他锁(X),意向共享锁(IS),意向排他锁(IX)。咱们能够经过如下表格来总结上面这四种的共存逻辑关系:
    图片描述
  10. 虽然Innodb的锁定机制和Oracle有很多相近的地方,可是二者的实现却大相径庭。
  11. 总的来讲,Oracle锁定数据是经过须要锁定的某行记录所在的物理block上的事务槽上表级锁定信息,而Innodb的锁定则是经过在指定数据记录的第一个索引键以前和最后一个索引键以后的空域空间上标记锁定信息而实现的。
  12. Innodb的这种锁定实现方式被称为“NEXT-KEY locking”(间隙锁),由于query执行过程当中经过范围查找的话,他会锁定整个范围内全部的索引键值,即便这个键值并不存在。
  13. 间隙锁有一个致命弱点,就是当锁定一个范围键值以后,即便某些不存在的键值也会被锁定,而形成在锁定的时候没法插入锁定建值范围内的任何数据。在某些场景下这可能会对性能形成很大的危害,而Innodb给出的解释是为了阻止幻读的出现,因此他们选择间隙锁来实现锁定。
  14. 除了间隙锁给Innodb带来性能的负面影响以外,经过索引实现锁定的方式还存在其余几个较大的性能隐患:数据库

    (1)当query没法利用索引的时候,Innodb会放弃使用行级锁定而该用表级锁定,形成并发性能的下降;
    (2)当query使用的索引并不包含全部过滤条件的时候,数据检索使用到的索引键所指向的数据可能有部分并不属于该query的结果集的行列,可是也会被锁定,由于间隙锁锁定的是一个范围,而不是具体的索引键;
    (3)当query使用索引定位数据的时候,若是使用的索引键同样但访问的数据行不一样的时候(索引只是过滤条件的一部分),同样会被锁定。

行级锁定 - Innodb各事务隔离级别下锁定及死锁

  1. Innodb实现的在ISO/ANSI SQL92规范中锁定义的Read UnCommited,Read Commited,Repeatable Read和Serializable这四种事务隔离级别。同时,为了保证数据在事务中的一致性,实现了多版本数据访问。
  2. 以前咱们已经介绍过,行级锁定确定会带来死锁问题,Innodb也不例外。
  3. 在Innodb中当系统检测到死锁产生以后是如何来处理的?编程

    (1)在Innodb的事务管理和锁定机制中,有专门检测死锁的机制,会在系统中产生死锁以后的很短期内就检测到该死锁的存在。
    (2)当Innodb检测到死锁以后,Innodb会经过相应的判断来选这产生死锁的两个事务中较小的事务来回滚,而让另一个较大的事务成功完成。
    (3)那Innodb是以什么为标准断定事务的大小呢?实际上在Innodb发现死锁以后,会计算出两个事务各自插入、更新或者删除的数据量来断定两个事务的大小。也就是说哪一个事务所改变的记录条数越多,在死锁中就越不会被回滚掉。
    (4)有一点要注意,当产生死锁的场景中涉及到不止Innodb存储引擎的时候,Innodb是没办法检测到该死锁的,这时候就只能经过锁定超时限制来解决该死锁了。

合理利用锁机制优化MySQL

MyISAM表锁优化建议

  1. 对于MyISAM存储引擎,虽然使用表级锁定在锁定实现的过程当中比实现行级锁定或者页级锁定所带来的附加成本都要小,锁定自己所消耗的资源也是最少,可是,因为锁定的颗粒度较大,因此形成锁定资源的争用状况也会比其余的锁定级别要多,从而在较大程度上会下降并发处理能力。
  2. 因此,在优化MyISAM存储引擎锁定问题的时候,最关键的是如何让其提升并发度。
  3. 因为锁定级别是不可能改变的了,因此咱们首先须要尽量让锁定的时间变短,而后就是让可能并发进行的操做尽量的并发。
  4. 缩短锁定时间架构

    (1)缩短锁定时间,提及来容易,实际作起来不简单。如何让锁定时间尽量的缩短呢?惟一的办法就是让咱们的query执行时间尽量的短
    (2)尽可能减小大的复杂query,将复杂query分拆成几个小的query分布进行中
    (3)尽量的创建足够高效的索引,让数据检索更迅速
    (4)尽可能让MyISAM存储引擎的表只存放必要的信息,控制字段类型
    (5)利用合适的机会优化MyISAM表数据文件
  5. 分离能并行的操做并发

    (1)MyISAM表锁是读写互相阻塞的表锁,因此可能有些人会认为在MyISAM存储引擎的表上就只能彻底的串行化,没办法并行了。
    (2)可是不要忘记,MyISAM的存储引擎还有一个很是有用的特性,那就是个Concurrent Insert(并发插入)的特性。
    (3)MyISAM存储引擎有一个控制是否打开Concurrent Insert(并发插入)功能的参数选项:concurrent_insert,能够设置为0、一、2
        【1】concurrent_insert=2,不管MyISAM存储引擎的表数据文件的中间部分是否存在由于删除数据而留下的空闲空间,都容许在数据文件尾部进行Concurrent Insert; 
        【2】concurrent_insert=1,当MyISAM存储引擎表数据文件中间不存在空闲空间的时候,能够从文件尾部进行Concurrent Insert;
        【3】concurrent_insert=0,不管MyISAM存储引擎的表数据文件的中间部分是否存在因删除数据而留下的空闲空间,都不容许Concurrent Insert。
  6. 合理利用读写优先级分布式

    (1)在本章各类锁定分析一节中咱们了解到,mysql的表级锁定对于读和写是有不一样优先级的,默认状况下是写优先级大于读优先级。
    (2)因此咱们能够根据各自系统环境的差别决定读与写的优先级。
    (3)若是咱们的系统是一个以读为主,并且要优先保证查询性能的话,咱们能够经过设置系统参数选项low_priority_updates=1,将写的优先级设置为比读优先级低,这样mysql就会尽可能先处理读请求。
    (4)这里咱们彻底能够利用这个特性,将concurrent_insert参数设置为1,甚至若是数据被删除的可能性很小的时候,若是对暂时性的浪费少许空间并非特别的在意的话,将concurrent_insert参数设置为2均可以尝试。固然,数据文件中间留有空域空间,在浪费空间的时候,还会形成在查询的时候须要读取更多的数据,因此若是删除量不是很小的话,仍是建议将concurrent_insert设置为1更为合适。

InnoDB行级锁优化建议

  1. InnoDB存储引擎因为实现了行级锁,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会更高一些,可是在总体并发处理能力方面要远远因为MyISAM的表级锁定。
  2. 当系统并发量较高的时候,Innodb的总体性能和MyISAM相比就会有比较明显的优点了。
  3. 要想合理的利用InnoDB的行级锁,作到扬长避短,咱们必须作好如下工做高并发

    (1)尽量让全部数据检索都经过索引来完成,从而避免Innodb由于没法经过索引健加锁而升级为表锁
    (2)合理涉及索引,让InnoDB在索引键上面加锁尽量准确,尽量的缩小锁定范围,避免形成没必要要的锁定而影响query的执行
    (3)尽量减小基于范围的数据检索过滤条件,避免由于间隙锁带来的负面影响而锁定不应锁定的记录
    (4)尽可能控制事务的大小,减小锁定的资源量和锁定时间长度
    (5)在业务环境容许的状况下,尽可能使用较低级别的事务隔离,以减小mysql由于事务隔离级别所带来的附加成本
  4. 因为InnoDB的行级锁定和事务性,因此确定会产生死锁,建议:

    (1)相似业务模块中,尽量按照相同的访问顺序来访问,防止产生死锁
    (2)在同一事务中,尽量作到一次锁定所须要的全部资源,减小死锁产生几率
    (3)对于很是容易产生死锁的业务部分,能够尝试使用升级锁定颗粒度,经过表级锁定来减小死锁产生的几率

系统锁定争用状况查询

  1. 对于两种锁定级别,MySQL内部有两组专门的状态变量记录系统内部锁资源争用状况,咱们先看看MySQL实现的表级锁定的争用状态变量:
  2. 这里有两个状态变量记录MySQL内部表级锁定的状况,两个变量说明以下:

    (1)Table_locks_immediate:产生表级锁定的次数; 
    (2)Table_locks_waited:出现表级锁定争用而发生等待的次数;
  3. 两个状态值都是从系统启动后开始记录,没出现一次对应的事件则数量加 1。若是这里的Table_locks_waited状态值比较高,那么说明系统中表级锁定争用现象比较严重,就须要进一步分析为何会有较多的锁定资源争用了。
  4. 对于Innodb所使用的行级锁定,系统中是经过另一组更为详细的状态变量来记录的,以下:

    mysql> show status like 'innodb_row_lock%';

    图片描述

  5. Innodb的行级锁定状态变量不只记录了锁定等待次数,还记录了锁定总时长,每次平均时长,以及最大时长,此外还有一个非累积状态量显示了当前正在等待锁定的等待数量。对各个状态量的说明以下:

    (1)Innodb_row_lock_current_waits:当前正在等待锁定的数量;
    (2)Innodb_row_lock_time:从系统启动到如今锁定总时间长度;
    (3)Innodb_row_lock_time_avg:每次等待所花平均时间; 
    (4)Innodb_row_lock_time_max:从系统启动到如今等待最常的一次所花的时间;
    (5)Innodb_row_lock_waits:系统启动后到如今总共等待的次数;
  6. 对于这 5 个状态变量,比较重要的主要是 Innodb_row_lock_time_avg(等待平均时长),Innodb_row_lock_waits(等待总次数)以及Innodb_row_lock_time(等待总时长)这三项。尤为是当等待次数很高,并且每次等待时长也不小的时候,咱们就须要分析系统中为何会有如此多的等待,而后根据分析结果着手指定优化计划。
  7. 此外,Innodb出了提供这五个系统状态变量以外,还提供的其余更为丰富的即时状态信息供咱们分析使用。能够经过以下方法查看:

    (1)经过建立Innodb Monitor表来打开Innodb的monitor功能:
         mysql> create table innodb_monitor(a int) engine=innodb;
    
         Query OK, 0 rows affected (0.07 sec) 
    (2)而后经过使用“SHOW INNODB STATUS”查看细节信息(因为输出内容太多就不在此记录了); 可能会有读者朋友问为何要先建立一个叫innodb_monitor的表呢?由于建立该表实际上就是告诉Innodb咱们开始要监控他的细节状态了,而后 Innodb就会将比较详细的事务以及锁定信息记录进入MySQL的error log中,以便咱们后面作进一步分析使用。

参考连接

https://www.cnblogs.com/jesse...

相关文章
相关标签/搜索