1. 一致性锁定读(consistent locking read)
在某些情况下,用户需要显示地对数据库读取操作进行加锁,以保证数据逻辑的一致性。而这要求数据库支持加锁语句,即使是对于SELECT的只读操作。InnoDB存储引擎对于SELECT语句提供了两种一致性的锁定读(locking read)操作:
- SELECT...FOR UPDATE :对读取的行记录加一个X(独占)锁
- SELECT...LOCK IN SHARE MODE:对读取的行记录加一个S(共享)锁
2. 一致性非锁定读(consistent nonlocking read)
是指InnoDB存储引擎通过行多版本控制(multi versioning)的方式来读取当前执行时间数据库中行的数据。如果读取的行正在执行DELETE或UPDATE操作,这时读取操作不会因此去等待行上锁的释放,相反地,InnoDB存储引擎会去读取行的一个快照数据(快照数据其实就是当前行数据之前的历史版本,每行记录可能有多个版本,因此一行记录可能有不止一个快照数据,一般称这种技术为 行多版本技术。由此带来的并发控制,称之为多版本并发控制(Multi Version Concurrency Control, MVCC))。
非锁定读机制极大地提高了数据库的并发性,这也是InnoDB存储引擎的默认读取方式。但是在不同事务隔离级别下,读取的方式不一定相同。此外,即使都是使用非锁定的一致性读,但是对于快照数据的定义也各不相同。例如,在事务隔离级别READ COMMITTED 和REPEATABLE READ下,InnoDB都使用非锁定一致性读,但是它们对于快照数据的定义却不同:
- READ COMMITTED事务隔离级别:对于快照数据,非一致性读总数读取被锁定行的最新的快照数据。
- REPEATABLE READ事务隔离级别:对于快照数据,非锁定一致性读总是获取事务开始时的行数据版本。
一致性非锁定读不会在表上设置任何锁,所以其它会话可以对表进行读写操作。并且,一致性读不适用于特定的DDL语句如DROP TABLE、ALTER TABLE。另外,对于 INSERT INTO ... SELECT, UPDATE ... (SELECT)和CREATE TABLE ... SELECT 中未指定FOR UPDATE或LOCK IN SHARE MODE的SELECT默认情况下行为和READ COMMIT隔离级别下的普通SELECT一样,同一事务内设置和读取自己的新鲜快照。
3. MVCC
MVCC也被成为多版本并发控制,实现的核心在于快照机制,在InnoDb存储引擎中则是通过undo log来存储数据快照。undo log日志用于保证事务的原子性,所以undo log的持久化必须在在数据持久化之前,这样才能保证系统崩溃时,可以用undo log来回滚事务。
InnoDB存储引擎通过undo log保存了已更改行的旧版本的信息的快照,其默认隔离级别是Repeatable read,底层实现是为每一行数据增加了隐藏列用于实现MVCC。其中最关键的有两列,一个保存了行的创建时间,一个保存了行的删除时间(存储的并不是时间值,而是系统版本号)。每开始一个新的事务,系统版本号都会自动递增。事务开始的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号做比较。针对不同的CRUD操作而言:
- Select:InnoDb会根据以下两个条件来检查每行记录:
a. InnoDb只查找版本早于当前事务版本的数据行(也就是行的版本号小于或等于事务的系统版本号),这样可以确保事务读取的数据行,要么是在事务开始前已经持久化存在的,要么是在自身事务期间插入或修改的;
b. 行的删除版本要么未定义,要么大于当前事务版本号,这样可以确保事务读取到的行,在事务开始之前未被删除。
- Insert:InnoDb为新插入的每一行保存当前系统版本号作为行版本号。
- Delete:InnoDb为删除的每一行保存当前系统版本号作为行删除标识。
- Update:InnoDb会插入一行新记录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为删除标识(实际是通过undo log来备份旧记录的)。
当然,还有一些其它列,用于其它作用:
- 回滚指针:用于事务回滚
- DATA_TRX_ID:标记了最新更新这条行记录的transaction id,每处理一个事务,其值自动+1
- DATA_ROLL_PTR:指向当前记录项的rollback segment的undo log记录,找之前版本的数据就是通过这个指针
- DB_ROW_ID:当由innodb自动产生聚集索引时,聚集索引包括这个DB_ROW_ID的值,否则聚集索引中不包括这个值,这个用于索引当中
- DELETE BIT:用于标识该记录是否被删除,这里的不是真正的删除数据,而是标志出来的删除,真正意义的删除是在commit的时候