MySQL性能调优与架构设计--第七章MySQL数据库锁定机制

为了保证数据的一直完整性,出现了各种锁定机制。而锁定机制决定了一个数据库的并发处理能力和性能。


数据库的锁定机制简单来说就是数据库为了保证数据的一致性而使各种共享资源在被并发访问时变得有序所设计的一种规则。


三种类型的锁定机制:行级锁定、页级锁定和表级锁定。
行级锁定:最小粒度锁定,更大的并发处理能力,但是也最容易死锁,且对资源消耗比较大。
表级锁定:最大粒度锁定,并发最艰难,消耗资源最少。
页级锁定:并发能力和性能开销,位于行级锁定和表级锁定之间。


在MySQL数据库中,使用表级锁定的是MyISAM、Memory、CSV、等一些非事务性存储引擎,行级锁定的主要是InnoDB和NDB Cluster,页级锁定主要是BerkeleyDB。




各种锁定机制分析:


表级锁定:
MySQL的表级锁定主要分为两种类型,一种是读锁定,另一种是写锁定。在MySQL中,主要通过四个
队列来维护这两种锁定:两个存放当前正在锁定中的读和写锁定信息,另外两个存放等待中的读写锁定
信息,如下:
Currentread-lockqueue(lock->read)
Pendingread-lockqueue(lock->read_wait)
Currentwrite-lockqueue(lock->write)
Pendingwrite-lockqueue(lock->write_wait)


锁定类型 说明
IGNORE 当发生锁请求的时候内部交互使用,在锁定结构和队列
中并不会有任何信息存储
UNLOCK 释放锁定请求的交互用所类型
READ 普通读锁定
WRITE 普通写锁定
READ_WITH_SHARED_LOCKS 在Innodb中使用到,由如下方式产生
如:SELECT...LOCKINSHAREMODE
READ_HIGH_PRIORITY 高优先级读锁定
READ_NO_INSERT 不允许ConcurentInsert的锁定
WRITE_ALLOW_WRITE 这个类型实际上就是当由存储引擎自行处理锁定的时
候,mysqld允许其他的线程再获取读或者写锁定,因为
即使资源冲突,存储引擎自己也会知道怎么来处理
WRITE_ALLOW_READ 这种锁定发生在对表做DDL(ALTER TABLE ...)的时
候,MySQL可以允许其他线程获取读锁定,因为MySQL是
通过重建整个表然后再RENAME而实现的该功能,所在整
个过程原表仍然可以提供读服务
WRITE_CONCURRENT_INSERT 正在进行ConcurentInsert时候所使用的锁定方式,该
锁定进行的时候,除了READ_NO_INSERT之外的其他任何
读锁定请求都不会被阻塞
WRITE_DELAYED 在使用INSERTDELAYED时候的锁定类型
WRITE_LOW_PRIORITY 显 示 声 明 的 低 级 别 锁 定 方 式 , 通 过 设 置
LOW_PRIORITY_UPDAT=1而产生
WRITE_ONLY 当在操作过程中某个锁定异常中断之后系统内部需要进
行CLOSETABLE操作,在这个过程中出现的锁定类型就
是WRITE_ONLY




读锁定:
一个新的客户端请求在申请获取读锁定资源的时候,需要满足两个条件:
1、请求锁定的资源当前没有被写锁定;
2、写锁定等待队列(Pendingwrite-lockqueue)中没有更高优先级的写锁定等待;




Innodb锁定模式及实现机制:
InnoDB的锁定机制分为共享锁和排他锁。为了实现表级锁定,还存在意向共享锁和意向排他锁。
共享锁(S)排他锁(X)意向共享锁(IS)意向排他锁(IX)
共享锁(S) 兼容 冲突 兼容 冲突
排他锁(X) 冲突 冲突 冲突 冲突
意 向 共 享 锁(IS)兼容 冲突 兼容 兼容
意 向 排 他 锁(IX)冲突 冲突 兼容 兼容


Oracle锁定数据主要是通过在需要锁定的某行记录所在的物理Block上的事务槽上添加锁定信息。
InnoDB的锁定机制是通过在指向数据记录的第一个索引键之前和最后一个索引键之后的空域空间标记锁定信息实现,被成为“NEXT-KEY locking”。
间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜
的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成
很大的危害。而Innodb给出的解释是为了组织幻读的出现,所以他们选择的间隙锁来实现锁定。


除了间隙锁给Innodb带来性能的负面影响之外,通过索引实现锁定的方式还存在其他几个较大的性能隐患:
●当Query无法利用索引的时候,Innodb会放弃使用行级别锁定而改用表级别的锁定,造成并发性能的降低;
●当Quuery使用的索引并不包含所有过滤条件的时候,数据检索使用到的索引键所只想的数据可
能有部分并不属于该Query的结果集的行列,但是也会被锁定,因为间隙锁锁定的是一个范
围,而不是具体的索引键;
●当Query在使用索引定位数据的时候,如果使用的索引键一样但访问的数据行不同的时候(索引只是过滤条件的一部分),一样会被锁定。




*********
上面的内容两点需要说明:
1、MyISAM在5.0和5.5两个版本中测试, 是测试不出来表级锁定的,因为对于非事务的引擎是一直自动提交的(对于非事务的表,如MyISAM表或内存表(Memory Table),改变AUTOCOMMIT值没有意义,这些表本质上一直操作在AUTOCOMMIT模式)。
2、InnoDB在5.0中,是遵循的索引键锁定机制,也就是没索引键会升级为表锁定;但是5.5,没有索引键,也是行级索引机制。
*********


Innodb实现的在ISO/ANSISQL92规范中所定义的ReadUnCommited,ReadCommited,Repeatable
Read和Serializable这四种事务隔离级别。同时,为了保证数据在事务中的一致性,实现了多版本数据
访问。






合理利用锁定机制 优化MySQL:


MyISAM表锁定优化建议:
关键是如何提高并发度。
缩短锁定时间:
·尽量减少打的复杂的Query,将复杂的Query分拆成几个小的Query分步进行;
·尽可能的建立足够高效的索引,让数据检索更迅速;
·尽量让MyISAM存储引擎表只存放必须要的信息,控制字段类型;
·利用合适的机会优化MyISAM表数据文件。
分离能力并行操作:
MyISAM存储引擎有一个控制是否打开ConcurrentInsert功能的参数选项:concurrent_insert,可
以设置为0,1或者2。三个值的具体说明如下:
a) concurrent_insert=2,无论MyISAM存储引擎的表数据文件的中间部分是否存在因为删除数据
而留下的空闲空间,都允许在数据文件尾部进行ConcurrentInsert;
b) concurrent_insert=1,当MyISAM存储引擎表数据文件中间不存在空闲空间的时候,可以从文
件尾部进行ConcurrentInsert;
c) concurrent_insert=0,无论MyISAM存储引擎的表数据文件的中间部分是否存在因为删除数据
而留下的空闲空间,都不允许ConcurrentInsert。
合理利用读写优先级:
在本章各种锁定分析一节中我们了解到了MySQL的表级锁定对于读和写是有不同优先级设定的,默
认情况下是写优先级要大于读优先级。所以,如果我们可以根据各自系统环境的差异决定读与写的优先
级。如果我们的系统是一个以读为主,而且要优先保证查询性能的话,我们可以通过设置系统参数选项
low_priority_updates=1,将写的优先级设置为比读的优先级低,即可让告诉MySQL尽量先处理读请
求。当然,如果我们的系统需要有限保证数据写入的性能的话,则可以不用设置low_priority_updates
参数了。




Innodb行锁优化建议:
要想合理利用Innodb的行级锁定,做到扬长避短,我们必须做好以下工作:
a) 尽可能让所有的数据检索都通过索引来完成,从而避免Innodb因为无法通过索引键加锁而升级
为表级锁定;
b) 合理设计索引,让Innodb在索引键上面加锁的时候尽可能准确,尽可能的缩小锁定范围,避免
造成不必要的锁定而影响其他Query的执行;
c) 尽可能减少基于范围的数据检索过滤条件,避免因为间隙锁带来的负面影响而锁定了不该锁定
的记录;
d) 尽量控制事务的大小,减少锁定的资源量和锁定时间长度;
e) 在业务环境允许的情况下,尽量使用较低级别的事务隔离,以减少MySQL因为实现事务隔离级
别所带来的附加成本;
由于Innodb的行级锁定和事务性,所以肯定会产生死锁,下面是一些比较常用的减少死锁产生概率
的的小建议,读者朋友可以根据各自的业务特点针对性的尝试:
a) 类似业务模块中,尽可能按照相同的访问顺序来访问,防止产生死锁;
b) 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;
c) 对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁
产生的概率;








系统锁定争用情况查询:
对于两种锁定级别,MySQL内部有两组专门的状态变量记录系统内部锁资源争用情况,我们先看看
MySQL实现的表级锁定的争用状态变量:
mysql>showstatuslike'table%';
+-----------------------+-------+
|Variable_name |Value|
+-----------------------+-------+
|Table_locks_immediate|100 |
|Table_locks_waited |0 |
+-----------------------+-------+
这里有两个状态变量记录MySQL内部表级锁定的情况,两个变量说明如下:
●Table_locks_immediate:产生表级锁定的次数;
●Table_locks_waited:出现表级锁定争用而发生等待的次数;


对于Innodb所使用的行级锁定,系统中是通过另外一组更为详细的状态变量来记录的,如下:
mysql>showstatuslike'innodb_row_lock%';
+-------------------------------+--------+
|Variable_name |Value |
+-------------------------------+--------+
|Innodb_row_lock_current_waits|0 |
|Innodb_row_lock_time |490578|
|Innodb_row_lock_time_avg |37736 |
|Innodb_row_lock_time_max |121411|
|Innodb_row_lock_waits |13 |
+-------------------------------+--------+
●Innodb_row_lock_current_waits:当前正在等待锁定的数量;
●Innodb_row_lock_time:从系统启动到现在锁定总时间长度;
●Innodb_row_lock_time_avg:每次等待所花平均时间;
●Innodb_row_lock_time_max:从系统启动到现在等待最常的一次所花的时间;
●Innodb_row_lock_waits:系统启动后到现在总共等待的次数;

你可能感兴趣的:(MySQL性能调优与架构设计--第七章MySQL数据库锁定机制)