Mysql中的Innodb事务和锁详解
作者:feiyingHiei
Innodb事务和锁
一. 事务隔离级别
- READ UNCOMMITED :读未提交,事务之间可以看到彼此之间正在修改的内容,会出现所谓的脏读现象
- READ COMMITED : 读已提交,只能读取已经提交的事务修改的数据,不会出现脏读的现象,但会出现不可重复读和幻读的情况
- REPEATABLE READ : 可重复度,在innodb中解决了不可重复读和幻读的问题
- SERIALIZED : 串行化, 这个使用的比较少,隔离性最强,性能也最差
二. 锁类型
- S LOCK : 共享锁 : S锁与S锁相互兼容,与X锁不兼容
- X LOCK :排他锁 :与其他所有锁类型不兼容,简单的可以理解为读写锁即可
- 意向锁 (IS, IX) : 主要是用来表达加锁的一种层次概念,比如希望在行级别加X锁,那么就需要在表级和页级别加上IX锁,意向锁整体上兼容性会比较好,如果当前表中已经有了多个行级锁,如果希望尝试加表锁,如果没有意向锁的话,那么就需要取检测当前行锁的加锁情况,但是有了意向锁,就可以快速判断当前尝试加表锁可能是需要等待的。
监控方法
- SHOW ENGINE INNODB STATUS 命令查看innodb引擎信息,其中包含当前执行事务的状态和锁的状态
- 通过information_schema提供的表来排查可能存在的问题
- a. information_schema.innodb_trx
- b. information_schema.innodb_locks
- c. innodb_lock_waits
三.锁释放的时机
lock通常在事务commit或是rollback之后才会释放
四. 一致性非锁定读
这是非常常见的问题,事务中会有大量的select操作,select操作通常情况下是不会对数据做加锁操作的, 这样保证了数据的并发性能,在不同的事务隔离级别下,非一致性锁定读的行为是不太一样的
- READ COMMITED : 读取undo_log中该行最新的一条快照数据
- READ REPETABLE : 读取undo_log中,最近的早于当前事务开启时间的快照数据
五. 加锁算法
上文描述了锁的类型,即排它锁,共享锁,意向锁之类,这里所说的是加锁的算法,即为innodb使用了什么样的加锁策略来处理实际场景中的并发问题。
- record lock : 行锁,对单个行记录进行加锁,行是innodb的最小数据管理单位,innodb中有专门的数据结构来存储和管理行记录,我们所有的查询的数据最终都会落到存储引擎的行上
- gap lock : 间隙锁,即对间隙加锁,间隙其实就这里只是针对间隙,不包括行(todo ,间隙的概念需要搞一下)
- next-key lock : 对行和间隙和行都进行加锁
netx-key lock算法主要解决的问题就是幻读问题,因此这个算法只在 RR事务隔离级别中会使用
疑问? 不是MVCC可以在无锁的情况下解决幻读的问题吗,为什么还要上next-key lock这么重的算法呢?这里就要引出两个概念了,一致性非锁定读(快照读) 和一致性锁定读(当前读) 的概念了
一致性非锁定读 : 我们平时使用的select * from table 这种查询语句,都是快照读,在innodb RC和RR级别下都是通过MVCC机制来实现的,过程中是不加锁的,读取的数据未必是当前行中的真实的数据。
一致性锁定读 : select * from table for update 或者select * from table in shared mode 在这种情况下RC基本是会对对应的行加锁,RR级别下就会使用next-key算法加锁。
六、redo日志
redo日志是物理日志,记录的页的物理修改日志
七、undo日志
undo日志是逻辑日志,记录的是操作的sql逻辑,通常我们在修改数据的时候,先对行加锁,然后将该版本的数据拷贝到undo日志中,然后修改当前行的操作事务id,修改该行事务,然后把回滚指针指向und日志。
七、MVCC
多版本控制(MVCC)在许多关系型数据库中都有实现,innodb中主要是依赖undo日志来实现,其中涉及到的一个比较重要的概念就是read veiw
- low_limit_id :最大活跃事务id
- up_limit_id : 最小活跃事务id
- trx_ids : 活跃事务集合
- 如果当前trx_id > low_limit_id,那数据一定是不可见的,因为数据是在当前事务开启后才修改的
- 如果当前trx_id < up_limit_id,那么数据一定是可见的,因为事务在当前事务开启前就已经提交了
- 如果trx_id 在这之间,那么如果trx_id在trx_ids中,说明事务还没有提交,那么数据就不可见, 否则事务就是可见的。
无论是在什么事务隔离级别下,基于read view的快照读机制都相同的,只是创建read view的机制不太一样。
在RR基本下,read view在事务开启的时候就创建完成了,二在RC级别下,每次查询都会重新创建read view。
如果一个当前事务为事务A, 另外一个事务是事务B, 如果事务A开启的时候,事务B已经提交了,那么无论在RR级别或是RC级别下,B提交的数据对于事务A都是可见的; 如果B事务在A事务开启之前就已经开启了,但是A启动的时候B事务还没有提交,那么先让无论RR级别还是RC级别,数据在快照读模式下也都是不可见的;如果B事务在A开启之前就开启,但是在A事务提交前B事务就提交了,此时对于RR级别而言,B的trx_id在trx_ids之中(trx_ids是在开启事务的时候就已经初始化了),那么RR级别下数据是可见的,但对于RC而言,重新生成的read view了,显然数据就是可见的。
通过上述的分析,通过mvcc可以在RR级别下快照读是完全解决了重复读和幻读的问题,RC级别还是会有不可重复读和幻读的问题,但是需要注意的是,MVCC只工作在快照读(一致性非锁定读)条件下,对于当前读的问题是无法走到MVCC的,所以一定要注意,MVCC只解决了快照读的幻读和不可重复读问题。
这里需要补充一个case来理解一下快照读与当前读的巨大区别
CREATE TABLE `user_info` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(10) NOT NULL DEFAULT '', `age` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) 事务隔离级别为RR
初始化数据
insert into user_info ('name', 'age') values ('jack', 1);
T1时刻A开启事务,然后启用快照读
select * from user_info where id = 1;
查询到的结果是
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | tom | 0 |
+----+------+-----+
T2时刻我们开启事务B, 并且执行
update user_info set age =1 where id = 1;
此时事务A中执行当前读
select * from user_info where id = 1 for update;
事务A将会被阻塞 此时提交事务B,事务A中显示的查询结果为
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | tom | 1 |
+----+------+-----+
然后我们再次执行一次快照读
select * from user_info where id = 1;
获得的结果为
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | tom | 0 |
+----+------+-----+
通过上述的例子,可以看到,快照读和当前读有着非常大的区别,在同一个事务当中,快照读和当前读返回的结果有可能是不一样的。
我们看到,mvcc保证了快照读每次读取的数据是一致,next-key算法保证了当前读是一致。
到此这篇关于Mysql中的Innodb事务和锁详解的文章就介绍到这了,更多相关Innodb事务和锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!