MySql性能(2)— 隔离级别带来的性能影响

什么叫做快照读和当前读

快照读:select 操作;
当前读:insert、delete、update、select...for update、select ... in share mode

1. 隔离级别之MVCC

  1. READ-UNCOMMITTED
    读未提交RU,在该隔离级别下会读到未提交事务所产生的数据更改,这意味着会读到脏数据。

  2. READ-COMMITTED
    读已提交RC,在这一隔离级别下,可以在SQL级别做到一致性读,每次SQL语句都会产生新的ReadView。这就意味着两次查询之间有别的事务提交了,是可以读到不一致的数据的。

  3. REPEATABLE-READ
    可重复读RR,在第一次创建ReadView后(例如事务执行的第一条SELECT语句),这个ReadView就会一直维持到事务结束,也就是说,在事务执行期间可见性不会发生变化,从而实现了事务内的可重复读。

  4. SERIALIZABLE
    序列化的隔离级别是最高等级的隔离级别,即将快照读升级为当前读(使用共享锁),实现读写互斥。

在上面的叙述中,RC和RR均使用了MVCC技术。但是RC事务中SQL读取的是最新快照,而RR事务读取的是唯一快照。于是RR便解决了不可重复读的问题。

依靠MVCC机制,RR的快照读可以解决不可重复读和大多数情况下的幻读

考虑这么一种情况:

1. SessionA开启事务

  • sessionA事务开启后,事务号为当前版本号 001
-- 确保当前隔离级别是 不可重复读
set session transaction isolation level repeatable read;
-- 查看当前版本
select @@transaction_isolation;
-- 开启事务
BEGIN;
-- 进行快照读
SELECT * FROM USER_T;
当前数据库的隔离级别为—RR.png
此时数据库只有一条数据.png

2. SessionB插入数据

  • SessionB开启事务,此时事务号为当前版本号-002
  • 插入查找,该行的版本号为事务号即-002
BEGIN;
-- 插入数据
insert user_t(id,user_name,password,age) 
            values('2','李丽','123','1');
COMMIT;

3. SessionA再次进行快照读

  • sessionA的事务号为-001,它的快照读只会读取表中行号小于等于001的数据,即只会读到一条数据,即避免了不可重复读+幻读
-- 进行快照读
SELECT * FROM USER_T;
并发事务的情况下,不会影响其他sessionA事务.png
  • 进行了Update操作,实际上执行了2个操作,即:
    • 插入一条新数据,此时的行号为当前事务号-001;
    • 删除原数据,将当前的事务号作为删除标识号-001;
-- 虽然快照读没有读到数据,但是使用update(当前读)可以修改数据
UPDATE user_t SET user_name='小三' where id='2';
  • 再次进行快照读,读取表中行号小于等于001的记录,此时就是读取到了2行。即同一个事务中,出现不同的结果集。
-- 再次进行快照读。
SELECT * FROM USER_T;
MySql性能(2)— 隔离级别带来的性能影响_第1张图片
快照读是出现幻觉—即出现幻读.png

需要注意的是:MVCC并不能解决幻读,事务A开始时快照读,事务B插入一条数据,事务A再次快照读是读取不到新数据的,但是事务A可是去修改新数据,而后进行快照读,便可以读取到两条数据,好像出现了幻读。

MVCC底层原理

MVCC的实现依赖:事务ID(DB_TRX_ID)和回滚指针(DB_ROLL_PTR)以及undo log和Read view实现的。

数据库每条记录中隐含包括3个列:这里用到的是事务ID(DB_TRX_ID)和回滚指针(DB_ROLL_PTR)

MySql性能(2)— 隔离级别带来的性能影响_第2张图片
事务库记录的隐藏列.png

MVCC的实现依赖的是undo log,事务中去增删改某列,该列会产生undo log,而该列回滚指针(DB_ROLL_PTR)会指向产生的undo log,每列的undo log都会连成一条链表,称为版本链。通过undo log中的事务ID(DB_TRX_ID)可以进行回滚或者返回某个时刻快照。

例如:事务1对记录进行了修改:

MySql性能(2)— 隔离级别带来的性能影响_第3张图片
事务1对age进行修改.png

例如:事务2再次对记录进行了修改:

MySql性能(2)— 隔离级别带来的性能影响_第4张图片
事务2对name进行修改.png

图片来源...

而Read View就是事务进行快照操作时生产的读视图(Read View)。

2. 间隙锁的作用

上述可以看到,即使快照读没有读取到表中的数据,我们使用update操作依旧将表中的数据进行了更新,即当前读看到最新的数据。

而InnoDB为了解决当前读幻读问题,引入了间隙锁。

时间上在事务中对一行加了行级锁之后,其他事务是不能改变它的。即当前读不存在不可重复读的情况。

而为了解决幻读,InnoDB锁的不止是一行,一般我们进行范围操作或没有使用唯一索引进行操作时,它会锁住多行数据。

在数据库参数中, 控制间隙锁的参数是:innodb_locks_unsafe_for_binlog, 这个参数默认值是OFF, 也就是启用间隙锁, 他是一个boolean值, 当值为true时表示disable间隙锁。那为了防止间隙锁是不是直接将innodb_locaks_unsafe_for_binlog设置为true就可以了呢? 不一定!而且这个参数会影响到主从复制及灾难恢复, 这个方法还尚待商量

上述的描述中,可以得到的信息就是:间隙锁可以解决主从复制的一致性问题。

mysql数据库的主从复制依靠的是binlog。而在mysql5.0之前,binlog模式只有statement格式。这种模式的特点:binlog的记录顺序是按照数据库事务commit顺序为顺序的

MySql性能(2)— 隔离级别带来的性能影响_第5张图片
事务的并发操作.png

而实际上,在statement模式下,binlog记录的日志为先插在删。就会导致master节点执行先删在插,但是slave节点却执行的是先插在删,最终导致数据的不一致。

而间隙锁就会使得sessionB的阻塞。此时会等待sessionA的提交sessionB才会提交,保证了主从的一致性。

而上面也说,在mysql5.0版本之前,binlog只存在statement模式,所以为了主从一致性,mysql默认的隔离策略为 可重复读。

在mysql5.0版本之后,binlog提供了row模式:即基于行的复制。故解决了sql主从节点执行不一致的问题。

3. 间隙锁的危害

在RR级别中,启用间隙锁的情况下,事务之间可能会互相影响,那么我们测试几种特殊的情况,以优化项目中sql语句。

解决MySQL可重复读——详解间隙锁

  • 事务A没有使用索引来操作表,将会对整个表加锁;
  • 加锁的基本单位是(next-key lock),它是前开后闭的原则;
  • 对唯一索引进行等值查询,next-key lock升级为行锁;
  • 对唯一索引进行范围查询,会访问到第一个不满足条件的第一个值为止;
  • 对普通索引进行等值查询,会向右遍历到最后一个不满足;

1. 表结构

CREATE TABLE `user_t` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(40) NOT NULL,
  `password` varchar(255) NOT NULL,
  `age` int(4) NOT NULL,
  PRIMARY KEY (`id`),   -- 唯一索引(主键)
  KEY `user_t_name` (`user_name`) -- 普通索引
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;
MySql性能(2)— 隔离级别带来的性能影响_第6张图片
表中字段.png
  1. 若操作无索引的字段

sessionA开启事务:

-- 开启事务
BEGIN;
-- 修改元素(where条件非索引)
UPDATE user_t set user_name='小鹿' where age='2'

sessionB提交事务

[SQL]
-- 插入数据
insert user_t(id,user_name,password,age) 
            values('10','李丽','12345','1');
[Err] 1205 - Lock wait timeout exceeded; try restarting transaction

结论:若是事务A没使用索引操作表,将会锁住整个表。

  1. 使用了普通索引

会对该索引字段的上下区间加间隙锁,防止其他事务影响;

  1. 使用范围查找
  • 会对寻找的范围加锁。

4. 是否可选用RC级别

mysql默认的隔离级别为RR,RR的特点如下:

  1. 保证了快照读的不可重复读,在一定程度上保证了不会发生幻读;
  2. 默认使用间隙锁,保证了当前读不会产生幻读,同时也可以保证binlog一致性;

但是引入一项新技术的同时,必定也会带来性能上的影响;

在RR隔离级别下,间隙锁性能低,且有几率造成死锁;mysql事务的范围操作非唯一索引操作时,使用间隙锁,防止其他事务的插入,可能会导致其他事务的等待。

在互联网项目中,是可以采用RC这个隔离级别的,需要注意的是binlog要选用low模式。而不可重复读的问题。实际上影响并不是很大,因为数据都已经commit了,读出来本身是没有太大问题的。

如何预防间隙锁引起的死锁

1)以固定的顺序访问表和行,将两个事务的sql顺序调整为一致,也能避免死锁。

2)大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小。

3)在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。

4)降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为gap锁造成的死锁。

5)为表添加合理的索引。可以看到如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大。
6)删除或更新数据时,先判断是否在表中存在,防止事务施加间隙锁;

推荐阅读

MYSQL RR隔离级别下MVCC及锁解读

MVCC原理分析

https://www.cnblogs.com/loleina/p/10344742.html

https://www.cnblogs.com/shoshana-kong/p/10516404.html

https://www.cnblogs.com/zengkefu/p/5678361.html

MySQL InnoDB 锁——官方文档

孤独烟——间隙锁与主从一致性

你可能感兴趣的:(MySql性能(2)— 隔离级别带来的性能影响)