时间:2021-07-01 10:21:17 帮助过:20人阅读
发现数据没有变,还是1000,说明可以避免脏读了。
接着A用户会话中将事务提交:
commit;
再次在B中查询,结果如下:
可以看到,B用户读取到了A用户提交的数据。这么做有什么问题么?那就是我们在会话B同一个事务中,读取到两次不同的结果。这就造成了不可重复读,就是两次读取的结果不同。
REPEATABLE READ解决了脏读的问题。该隔离级别保证了在同一个事务中多次读取同样记录结果是一致的。但是理论上,可重复读隔离级别还是无法解决另外一个幻读(Phantom Read)的问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(Phantom Row)。Mysql的RR是由“行排它锁+MVCC”一起实现的。
我们将用户B所在的会话当前事务隔离级别设置为repeatable read。
set session transaction isolation level repeatable read; start transaction;
接着在B中查询数据:
两条。
然后我们到A用户会话中插入一条数据:
insert into account(id,balance) value(3,1000);
在A中查看是否添加成功:
成功,有三条数据。
回到用户B会话中,再次查询:
发现没有变还是两条。这时,用户B想插入一条id=3,balance=1000的数据:
insert into account(id,balance) value(3,1000);会报错:
说是主键重复了,可是B用户刚刚查询并没有id=3的记录。这就是幻读现象。
我的理解是:不可重复读指的是update操作,而幻读指的是insert或delete操作。
SERIALIZABLE是最高的隔离级别。它通过强制事务串行执行,避免了前面说的幻读的问题。简单来说,SERIALIZABLE会在读取每一行数据都加锁,所以可能导致大量的超时和锁争用问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用该级别。
首先介绍一下几个概念:
读锁:也叫共享锁、S锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S 锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
写锁:又称排他锁、X锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。
表锁:操作对象是数据表。Mysql大多数锁策略都支持,是系统开销最低但并发性最低的一个锁策略。事务t对整个表加读锁,则其他事务可读不可写,若加写锁,则其他事务增删改都不行。
行级锁:操作对象是数据表中的一行。是MVCC技术用的比较多的。行级锁对系统开销较大,但处理高并发较好。
MVCC使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能。
Mysql Innodb中行记录的存储格式,除了最基本的行信息外,还会有一些额外的字段,这里主要介绍和MVCC有关的字段:DATA_TRX_ID和DATA_ROLL_PTR。
DATA_TRX_ID:用来标识最近一次对本行记录做修改(insert|update)的事务的标识符, 即最后一次修改(insert|update)本行记录的事务id。
DATA_ROLL_PTR:指写入回滚段(rollback segment)的 undo log record (撤销日志记录记录)。如果一行记录被更新, 则 undo log record 包含 ‘重建该行记录被更新之前内容‘ 所必须的信息。
借图举例:出自<<唐成-2016PG大会-数据库多版本实现内幕.pdf>>
当插入的是一条新数据时,记录上对应的回滚段指针为NULL
DB_TRX_ID记录了行的创建的时间,删除的时间在每个事件发生的时候,每行存储版本号,而不是存储事件实际发生的时间。每次事物的开始这个版本号都会增加。自记录时间开始,每个事物都会保存记录的系统版本号。依照事物的版本来检查每行的版本号。
InnoDB的MVCC,是通过在每行纪录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存了行的过期时间(或删除时间),当然存储的并不是实际的时间值,而是系统版本号。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行纪录的版本号进行比较。在REPEATABLE READ隔离级别下,MVCC具体的操作如下:
SELECT
InnoDB会根据以下两个条件检查每行纪录:
INSERT
DELETE
UPDATE
优点:
保存这两个额外系统版本号,使大多数读操作都可以不用加锁。这样设计使得读数据操作很简单,性能很好。
缺点:
每行纪录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。
读到这里,也许会有一个疑问,考虑如下执行序列:
按照之前的Select规则,会话B 的事务是在 会话A的后面开启的,那么B的事务版本号大于A的事务版本号。这样在A中插入的数据在未提交的情况下,B可以读到A修改的数据,这不就自相矛盾了么?其实不然,InnoDB每个事务在开始的时候,会将当前系统中的活跃事务列表(trx_sys->trx_list)创建一个副本(read view),然后一致性读去比较记录的tx id的时候,并不是根据当前事务的tx id,而是根据read view最早一个事务的tx id(read view->up_limit_id)来做比较的,这样就能确保在事务B之前没有提交的所有事务的变更,B事务都是看不到的。如下图所示:
参考链接:https://www.jianshu.com/p/db334404d909
浅析Mysql的隔离级别及MVCC
标签:副本 默认 高并发 ted imageview 设置 自动 工作 delete