SQL 标准的事务隔离级别包括四种:
读未提交
一个事务还没提交时, 它做的变更就能被别的事务看到;
带来的问题是: 脏读, 读取到了未提交的数据.
读已提交
一个事务提交之后, 它做的变更才会被其他事务看到;
带来的问题是: 数据不可重复读.
可重复读
一个事务执行过程中看到的数据, 总是跟这个事务在启动时看到的数据是一致的;当然在可重复读隔离级别下, 未提交变更对其他事务也是不可见的.
带来的问题: 幻读, 当一个事务读取数据行后, 另一个事务又修改了该数据, 第一个事务再次读取该数据行时, 出现数据两次读取不一致的情况.
串行化读
当出现读写锁冲突的时候, 后访问的事务必须等前一个事务执行完成, 才能继续执行.
MySQL默认的事务级别就是可重复读的, 那我们平时用的时候, 怎么没出现幻读的问题呢?
那是因为 MySQL实现了多版本并发控制(MVCC).
MVCC又是怎么样实现的呢?
多版本并发控制MVCC
在 MySQL 中, MVCC 是利用回滚日志(undo log)和事务ID(txID) 配合实现的.
实际上每条数据更新时, 都会同时将原数据记录到回滚日志(undo log)中. 通过回滚操作, 都可以得到前一个事务对应的值.
而每次记录的数据, 就被称为 read view, 也就是一个只读的视图. 里面不仅有数据信息, 还有事务版本信息.
假设一个值从 1 被按顺序改成了 2, 3, 在回滚日志里面就会有类似下面的记录.
如果每个事务请求又是怎么从回滚日志中找到对应的 read-view的呢?
这就要从数据行说起了.
数据行隐藏字段
每行数据除了我们自定义的字段外, 还有数据库隐式定义的一些字段.
DB_TRX_ID 6b, 最近修改(修改/插入)事务ID, 记录创建这条记录/最后一次修改该记录的事务ID
DB_ROLL_PTR 7b, 回滚指针, 指向这条记录的上一个版本信息(存储在回滚日志中)
DB_ROW_ID 6b, 隐含的自增ID(隐藏主键), 如果数据表没有主键, InnoDB会自动以DB_ROW_ID产生一个聚簇索引.
Info flags
4b, 包括删除flag等记录数据行状态字段
对照这些信息, 再看上面的read view, 就能发现MVCC的实现机制了.
在执行select时, 就是使用这些隐藏列配合查找对应数据的.
Innodb只查找对应事务ID(DB_TRX_ID)的数据行; 当前数据行事务ID太高, 就到回滚日志中查找对应数据.
其他操作也会对这些隐藏字段进行维护和修改:
insert
新插入的数据保存当前事务ID(DB_TRX_ID).
delete
记录当前事务ID(DB_TRX_ID)和删除标识.
update
转变为insert和delete组合, insert的数据保存当前事务ID;
delete数据, 记录当前事务ID(DB_TRX_ID)和删除标识.
注意: 由于旧数据并不真正的删除, 所以必须对这些数据进行清理, Innodb会开启一个后台线程, 将删除事务ID小于当前系统版本的数据行, 这个过程叫做purge.