1、事务的隔离级别
1、读未提交
2、读已提交
3、可重复读
4、串行化
隔离性引发的并发问题
脏读:读了未提交,读取到未提交的数据。
不可重复读:读了已提交。同一事务两次读取结果不一致。
幻读:操作其他事物删除的数据报错,同一事物两次读取数据不一致。可重复读隔离级别,幻读,并不是说两次读取获取的结果集不同,幻读侧重的方面是某一次的 select 操作得到的结果所表征的数据状态无法支撑后续的业务操作。
而一些结论,可重复度级别不存在幻读,出现的幻读是异常,是因为自动把隔离级别降为读已提交,如A事务查询快照读,B事务插入数据,A事务修改的数据条件包含B事务插入的数据,再进行A事务查询操作,发现与第一次查询结果不同,多了B事务数据。次情况不能算作幻读。因此,结论,幻读紧紧存在于读已提交隔离级别中。
mysql 默认隔离级别:可重复度
oracle默认:读已提交
2、MVCC说明
事务的快照时间点(即下文中说到的Read View的生成时间)是以第一个select来确定。所以即便事务先开始,但是select在事务的update之类的语句后进行,那么它是可以获取到其他事物已提交的事务的对应的数据。
LBCC是基于锁的并发控制,因为锁的粒度过大,会导致性能的下降,因此提出了比LBCC性能更优越的方法MVCC。MVCC是Multi-Version Concurrent Control的简称,意思是基于多版本的并发控制协议,通过版本号,避免同一数据在不同事务间的竞争,只存在于InnoDB引擎下。它主要是为了提高数据库的并发读写性能,不用加锁就能让多个事务并发读写。MVCC的实现依赖于:三个隐藏字段、Undo log和Read View,其核心思想就是:只能查找事务 id 小于等于当前事务 ID 的行;只能查找删除时间大于等于当前事务 ID 的行,或未删除的行。接下来让我们从源码级别来分析下MVCC。
隐藏列
MySQL中会为每一行记录生成隐藏列,接下来就让我们了解一下这几个隐藏列吧。
(1)DB_TRX_ID:事务 ID,是根据事务产生时间顺序自动递增的,是独一无二的。如果某个事务执行过程中对该记录执行了增、删、改操作,那么InnoDB存储引擎就会记录下该条事务的 id。
(2)DB_ROLL_PTR:回滚指针,本质上就是一个指向记录对应的undo log的一个指针,大小为 7 个字节,InnoDB 便是通过这个指针找到之前版本的数据。该行记录上所有旧版本,在undo log中都通过链表的形式组织。
(3)DB_ROW_ID:行标识(隐藏单调自增 ID),如果表没有主键,InnoDB 会自动生成一个隐藏主键,大小为 6 字节。如果数据表没有设置主键,会以它产生聚簇索引。
(4)实际还有一个删除 flag 隐藏字段,既记录被更新或删除并不代表真的删除,而是删除 flag 变了。
如上图,DB_ROW_ID是数据库默认为该行记录生成的唯一隐式主键,DB_TRX_ID是当前操作该记录的事务ID,而DB_ROLL_PTR是一个回滚指针,用于配合undo日志,指向上一个旧版本
Read View
在可重复读隔离级别下,我们可以把每一次普通的select查询(不加for update语句)当作一次快照读,而快照便是进行select的那一刻,生成的当前数据库系统中所有未提交的事务 id 数组(数组里最小的id为min_id)和已经创建的最大事务id(max_id)的集合,即我们所说的一致性视图readview。在进行快照读的过程中要根据一定的规则将版本链中每个版本的事务id与readview进行匹配查询我们需要的结果。
快照读是不会看到别的事务插入的数据的。因此,幻读在“当前读”下才会出现。快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。MVCC只在 READ COMMITTED 和 REPEATABLE READ两个隔离级别下工作,其他两个隔离级别不和MVCC兼容。因为READ UNCOMMITTED总是读取最新的数据行,而不是符合当前事务版本的数据行,而SERIALIZABLE 则会对所有读取的行都加锁。事务的快照时间点(即下文中说到的Read View的生成时间)是以第一个select来确认的。所以即便事务先开始,但是select在后面的事务的update之类的语句后进行,那么它是可以获取前面的事务的对应的数据。