Innodb事务和锁总结

Innodb事务和锁总结

一. 事务隔离级别
  1. READ UNCOMMITED :读未提交,事务之间可以看到彼此之间正在修改的内容,会出现所谓的脏读现象
  2. READ COMMITED : 读已提交,只能读取已经提交的事务修改的数据,不会出现脏读的现象,但会出现不可重复读和幻读的情况
  3. REPEATABLE READ : 可重复度,在innodb中解决了不可重复读和幻读的问题
  4. SERIALIZED : 串行化, 这个使用的比较少,隔离性最强,性能也最差
二. 锁类型

S LOCK : 共享锁 : S锁与S锁相互兼容,与X锁不兼容
X LOCK :排他锁 :与其他所有锁类型不兼容,
简单的可以理解为读写锁即可
意向锁 (IS, IX) : 主要是用来表达加锁的一种层次概念,比如希望在行级别加X锁,那么就需要在表级和页级别加上IX锁,意向锁整体上兼容性会比较好,如果当前表中已经有了多个行级锁,如果希望尝试加表锁,如果没有意向锁的话,那么就需要取检测当前行锁的加锁情况,但是有了意向锁,就可以快速判断当前尝试加表锁可能是需要等待的。

监控方法

  1. SHOW ENGINE INNODB STATUS 命令查看innodb引擎信息,其中包含当前执行事务的状态和锁的状态
  2. 通过information_schema提供的表来排查可能存在的问题
    a. information_schema.innodb_trx
    b. information_schema.innodb_locks
    c. innodb_lock_waits
三.锁释放的时机

lock通常在事务commit或是rollback之后才会释放

四. 一致性非锁定读

这是非常常见的问题,事务中会有大量的select操作,select操作通常情况下是不会对数据做加锁操作的, 这样保证了数据的并发性能,在不同的事务隔离级别下,非一致性锁定读的行为是不太一样的

  1. READ COMMITED : 读取undo_log中该行最新的一条快照数据
  2. READ REPETABLE : 读取undo_log中,最近的早于当前事务开启时间的快照数据
五. 加锁算法

上文描述了锁的类型,即排它锁,共享锁,意向锁之类,这里所说的是加锁的算法,即为innodb使用了什么样的加锁策略来处理实际场景中的并发问题。
3. record lock : 行锁,对单个行记录进行加锁,行是innodb的最小数据管理单位,innodb中有专门的数据结构来存储和管理行记录,我们所有的查询的数据最终都会落到存储引擎的行上,具体行,页,表的概念可以参考《Innodb存储引擎》一书
2.gap lock : 间隙锁,即对间隙加锁,间隙其实就这里只是针对间隙,不包括行(todo ,间隙的概念需要搞一下)
4. 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算法保证了当前读是一致。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值