Mysql 为了保证高并发数据的准确性,提出了事物的隔离性质,不同事物的隔离性质导致数据的读取方式发生改变,目前有如下四种隔离级别: 目前序列化表示任何sql语句都会加锁,属于单线程工作,除特殊用途,并不推荐使用。下面是常见的3种事物隔离机制
事物A能读到B事物未提交的更改,实则1读到的数据为2更改的数据,但事物B回滚,事物A所读到的是脏数据。
事物A只能读取到B提交的数据,1≠2,但3=2,此时在同一个事物不可重复读取。同时2会对数据增加排他锁,导致3阻塞。
为了保证在同一个事物中,多次读取的数据一致,则需要在读取时增加锁,使得事物B阻塞,等待事物A提交释放锁再执行。 可以看到,不考虑MVCC的控制思想,其实数据库是通过锁机制来确保事物的隔离性质。基本可以总结为:
我们在网上可以查找到一系列的锁名词,如悲观锁、乐观锁、行级锁、表级锁、共享锁、排他锁。对锁可做如下分类: 不考虑MVCC的思想,在事物隔离级别为可重复读的如下情况下会造成死锁: 由于访问顺序不一致,事物A锁住A资源,事物B锁住B资源,后续又需要对方资源,导致互相死锁。Mysql处理此类锁会根据资源来强制要求某一事物回滚从而释放锁资源。 要解决死锁,第一种办法是优化执行顺序: 第二种办法是降级为读已提交:
上文多次提到不基于MVCC控制的情况下的锁机制,Mysql为了优化锁的机制,尽可能使读取不受限制,增加了MVCC控制逻辑来提高性能。上文中提到排他锁可使其它事物无法读取该资源,而MVCC(Multiversion Concurrency Control多版本并发控制)则可做到可读取。 下文引用了MySQL InnoDB MVCC 机制的原理及实现文章,但融入了一点个人的思想。
mysql对每一列隐藏了三个字段,分别为 DATA_TRX_ID: 记录最近更新这条行记录的事务 ID,大小为 6 个字节 DATA_ROLL_PTR: 表示指向该行回滚段(rollback segment)的指针,大小为 7 个字节,InnoDB 便是通过这个指针找到之前版本的数据。该行记录上所有旧版本,在 undo 中都通过链表的形式组织。 DB_ROW_ID: 行标识(隐藏单调自增 ID),大小为 6 字节,如果表没有主键,InnoDB 会自动生成一个隐藏主键,因此会出现这个列。另外,每条记录的头信息(record header)里都有一个专门的 bit(deleted_flag)来表示当前记录是否已经被删除。
可以看到加入当前事物ID为200,更新时(还未提交事物),将数据列进行复制,将原有数据DATA_TRX_ID替换为200,而DATA_ROLL_PTR指向备份的数据。这样的结果是数据库中的值已经发生改变,只是事物没有提交,其DATA_ROLL_PTR会指向原有的数据,当事物回滚,可根据这个数据链找到原有数据即可。此种做法为mysql 的undo处理机制。 注意: Mysql的Redo处理机制不是Undo的逆过程,Redo的应用场景在记录数据库的操作日志,防止数据库因数据库崩溃或系统崩溃,导致数据无法及时写入磁盘,数据丢失的场景,redo则可以在系统恢复正常后,重新执行redo链即可得到想要的数据。
MVCC 模式在可重复读或读已提交两种事物机制下生成ReadView的数组结构,用于维护当前事物提交后的当前所有事物ID。如A事物ID为200,则A开始后,ReadView为【200】,在A未结束的同时,B开启事物(ID为300),则B读到的ReadView为【200,300】,当A结束后,B根据隔离级别判断是否更新ReadView,如果是读已提交,则读到的ReadView为【300】,如果是可重复读,B读到的ReadView仍然为【200,300】。 假如一条数据的初始结构如下(其事物ID为100): A开启事物200,则读到的ReadView为【200】,此时A对数据做更新修改,会出现: 此时A未结束,B开启事物300,读取到的ReadView为【200,300】,此时B读取该数据时,查询到该数据的DATA_TRX_ID为200,B发现当前事物在ReadView数组里,则表示该数据为提交,便通过Undo log链查找到数据的上一个版本,其DATA_TRX_ID为100,100不在ReadView数组当中且小于数组的最小值,则表示100已被提交,所以B此时读到的数据为10。 当A提交事物后,B再一次读取该数据,此时根据不同隔离级别采用不同的方式。如果是可重复读,则ReadView不更新,B读到的ReadView数组仍然为【200,300】,此时数据库中的数据DATA_TRX_ID为200,B误以为此事物仍然没提交,便通过undo找到上一个版本的数据,所以可重复读仍然为10。如果是读已提交,则B读到的ReadView数组为【300】,则读取数据时发现DATA_TRX_ID为200,判断事物已经提交成功,则直接读取到20。
总结: MVCC打破了读取不能访问排他锁的资源问题,提高性能的同时仍然保持了事物隔离性的特征。