前言数据库的事务特性数据并发读写时遇到的一致性问题mysql事务的隔离级别MVCC的实现原理锁和隔离级别1 数据库的事务特性原子性:同一个事务里的操作是一个不可分割的,里面的 sql 要么一起执行,要不执行,是原子性隔离性:数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的"独立"环境执行。这意味着事务处理过程中的中间状态对外部是不可见的一致性:在事务开始和完成时,数据约束都必须保持一致状态持久性:事务完成之后,它对于数据的修改是永久性的,即使出现系统崩溃也能够保持持久2 数据并发读写时遇到的一致性问题脏读(针对未提交)两个事务同时进行,事务A修改了数据D,且事务A未提交,而事务B却可以读取到未提交的数据D,称之为脏读脏写两个事务同时尝试去更新某一条数据记录时,当事务A更新时,事务A还没提交,事务B就也过来进行更新,覆盖了事务 A 提交的更新数据,这就是脏写。一般要加锁解决不可重复读(针对已提交的 update)针对的是已经提交的事务修改的值,同时进行的其他事务给读取到了,事务内多次查询,多次读到的是别的已经提交的事务修改过的值,这就导致不可重复读幻读(针对已提交的 insert)事务读取到事务开始之后的插入数据,例如select * from table_user where id between 1 and 10 ,这条sql本应查出 1~9 的数据,id=10 此时不存在,之后其他事务再插入了一条 id=10 的记录。然后当前事务再次查询则会查出 10 条记录。这就是幻读和不可重复读的区别是,不可重复读的问题是读取最新的修改,幻读是读取到最新的插入数据3 mysql事务的隔离级别读未提交(READ UNCOMITTED,RU):对应脏读,可以读取到最新未提交的修改读已提交(READ COMMITTED,RC):一个事务能读取另一个事务已经提交的修改。其避免了脏读,但仍然存在不可重复读和幻读问题可重复读(REPEATABLE READ,RR):同一个事务中多次读取相同的数据返回的结果是一样的。其避免了脏读和不可重复读问题,但幻读依然存在串行化读(SERIALIZABLE):事务串行执行。避免了以上所有的问题4 MVCC 的实现原理 MVCC 全称Multi-Version Concurrency Control,其好处是读不加锁,读写不冲突,并发性能好MVCC 的 undo log 版本链InnoDB中每行数据都有隐藏列,隐藏列中包含了本行数据的事务ID trx_id、指向 undo log 的 roll_pointer 指针 基于undo log的版本链:前面说到每行数据的隐藏列中包含了指向 undo log 的指针 roll_pointer,而每条undo log 也会指向更早版本的undo log,从而形成一条版本链 readView 对于使用READ COMMITTED 和REPEATABLE READ 隔离级别的事务来说,都必须保证读到已提交事务修改过的记录,也就是说假如另一个事务修改了记录但尚未提交,是不能读取最新版本的记录的,其核心问题:需要判断 MVCC 版本链中的哪个版本是当前事务可见的。innodb 的解决方案 readView,readView 包含4个比较重要的属性m_ids :在生成ReadView 时,当前系统中活跃的读写事务 id 列表min_trx_id :表示在生成ReadView 时,当前系统中活跃的读写事务中最小的事务id,也就是m_ids 中的最小值max_trx_id :表示生成ReadView 时系统中应该分配给下一个事务的 id 值creator_trx_id :对应生成该ReadView 事务的idreadView 的访问步骤如果被访问版本的trx_id 属性值与ReadView 中的creator_trx_id 值相同,表示当前事务在访问它自己修改过的记录,该版本可以被当前事务访问。如果被访问版本的trx_id 属性值小于ReadView 中的min_trx_id 值,表明生成该版本的事务在当前事务生成ReadView 前已经提交,所以该版本可以被当前事务访问。如果被访问版本的trx_id 属性值在ReadView 的min_trx_id 和max_trx_id 之间,那就需要判断一下trx_id 属性值是不是在m_ids 列表中,如果在,说明创建ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView 时生成该版本的事务已经被提交,该版本可以被访问如果被访问版本的trx_id 属性值大于或等于ReadView 中的max_trx_id 值,表明生成该版本的事务在当前事务生成ReadView 后才开启,该版本不可被当前事务访问。反之可见如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据(undo log)。如果最后一个版本都不可见的话,那么就意味着该条记录对该事务完全不可见读已提交和可重复读利用 ReadView 实现快照读:读取的是快照版本,也就是历史版本 readView 里的数据 ,普通的 SELECT 就是快照读当前读:读取的是最新版本,UPDATE、DELETE、INSERT、SELECT ... LOCK IN SHARE MODE、SELECT ... FOR UPDATE 是当前读,需要加锁READ UNCOMMITTED :直接读取记录的最新版本就好READ COMMITTED :每次读取数据前都生成一个ReadView针对当前读,RC 隔离级别保证对读取到的记录加锁 (记录锁),存在幻读现象REPEATABLE READ :在第一次读取数据时生成一个ReadView针对当前读,RR 隔离级别保证对读取到的记录加锁 (记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入 (间隙锁),不存在幻读现象RR 从严格意义上并没解决幻读。如果事务一开始先 update 一条看不见的数据(前面没有当前读操作),再查询,则会多查出这条记录,此时也是发生了幻读5 锁和隔离级别RC、RR、SERIALIZABLE 级别的隔离,当前读都会需要借助锁实现MVCC 能实现多数情况避免幻读,但不能完全避免幻读的发生RR 隔离级别需要先 select ... for update 加锁进行当前读操作,才能防止幻读对于SERIALIZABLE 隔离级别的事务来说,InnoDB 规定使用加锁的方式来访问记录欢迎指正文中错误参考文章Mysql加锁过程详解探索Mysql锁机制(二)深入学习MySQL事务:ACID特性的实现原理MySQL事务隔离级别的实现原理 本文作者 : 潜行前行 本文链接 : https://www.cnblogs.com/cscw/p/16106338.html