[译]事务隔离级别
原文地址Transaction Isolation Levels
事务隔离级别
事务隔离是数据库基础能力之一。隔离(Isolation)表示ACID中的I;隔离级别是一个配置项,它可以用于调整当多个事务同时进行更新和查询操作时,MySQL的性能和可靠性、一致性、结果的可重复性之间的平衡。
InnoDB提供了四种隔离级别,SQL:1992标准中描述了这个四个级别:未提交读(READ_UNCOMMITTED),提交读(READ_COMMITTED),可重复读(REPEATABLE_READ),串行化(SERIALIZABLE)。InnoDB的默认级别是可重复读(REPEATABLE_READ)。
用户可以使用SET_TRANSACTION语句来更改当前会话中的隔离级别。如果要更改全局的隔离级别,对所有会话生效,在命令行或者配置文件中使用--transaction-isolation选项。更多隔离级别的设置语法,可以查看SET TRANSACTION Syntax。
InnoDB使用不同的锁机制来实现上述不同的隔离级别。对于需要保证ACID重要数据上的操作,可以使用默认的REPEATABLE_READ实现高级别的一致性。对于精确的一致性和结果可重复性要求不那么高的情形,比如大量数据的操作,减少锁的数量更加重要,这种情况下可以使用READ_COMMITTED甚至READ_UNCOMMITTED来降低一致性。SERIALIZABLE实现了比REPEATED_READ更严格的规则,它主要用于一些特殊的场合,比如XA事务,以及解决并发和死锁相关问题。
以下描述了MySQL如何支持不同的隔离级别。顺序按照最常用到最不常用的。
-
REPEATED READ
这是InnoDB的默认隔离级别,同一个事务中的连续读(Consistent reads)会读到第一个读操作建立的快照(snapshot)。这也就是说如果在同一个事务中进行简单的多次SELECT操作,这些操作相互是一致的。更多可以查看一致无锁读(Consistent Nonlocking Reads)。
对于加锁读(SELECT with FOR UPDATE or LOCK IN SHARE MODE)、UPDATE、DELETE等操作,锁取决于操作语句是否使用了唯一索引并且使用唯一检索条件,还是使用了范围检索条件。
- 对于使用唯一索引并且使用唯一检索条件的情形,InnoDB只会给指定的这行记录索引加锁,不会在前面的间隙上枷锁。
- 对于使用范围检索条件的情形,InnoDB会给扫描到的索引范围都加上锁,使用间隙锁和next-key锁来阻止其他会话往这个范围内插入数据。有关间隙锁和next-key锁,可以查看InnoDB Locking。
-
READ COMMITTED
这个级别下,在同一个事务中的连续读操作,每一个读都会设置自己独有的最新快照并从中读取数据。有关连续读操作,查看Consistent Nonlocking Reads。
对于加锁读(SELECT with FOR UPDATE or LOCK IN SHARE MODE)、UPDATE、DELETE等操作,InnoDB只会给索引记录加锁,而不会给记录前面的间隙加锁,这样就允许了其他事务往这些记录间插入数据。间隙锁只会用来做外键约束检查以及重复key检查。
由于没有使用间隙锁,会出现幻读问题,因为其他会话可能往间隙中插入了数据。有关幻读,查看Phantom Rows。
如果你使用了READ COMMITTED级别,你必须同时使用行级别的二进制日志。
使用READ COMMITTED级别还有其他的影响:
- 对于UPDATE和DELETE语句,InnoDB会锁住将要更新或者删除的行。MySQL计算WHERE语句后,就会把不匹配的行上的锁释放掉。这个大大降低了死锁的概率,但是仍然有可能会发生。
- 对于UPDATE操作,如果记录已经被锁住,InnoDB会做"半一致性(semi-consistent)"读,它返回最近一次提交版本的记录给MySQL,然后MySQL会决定这个记录是否和UPDATE语句的WHERE条件匹配。如果记录匹配(需要被更新),MySQL会重新读取这行记录,并且不会加锁也不等待锁。
考虑下面这个例子,先建立一个表:
CREATE TABLE t (a INT NOT NULL, b INT) ENGINE = InnoDB; INSERT INTO t VALUES (1,2),(2,3),(3,2),(4,3),(5,2); COMMIT;
在这种情况下,表没有索引,因此查找和索引遍历操作会使用隐藏的聚合索引来给记录加锁(查看Clustered and Secondary Indexes)。
假设此时一个用户使用如下语句进行UPDATE操作:SET autocommit = 0; UPDATE t SET b = 5 WHERE b = 3;
紧接着,另外一个用户使用如下语句进行UPDATE操作:
SET autocommit = 0; UPDATE t SET b = 4 WHERE b = 2;
InnoDB执行这些UPDATE时,会先对每一行都加上排它锁,然后决定是否更改它们。如果InnoDB没有修改这些行,那么会立即立即释放锁;否则,InnoDB会持有锁直到事务结束。这样的操作对事务处理的影响如下:
当使用默认的REPEATABLE READ级别时,如果第一个UPDATE拿到了排它锁,并且没有释放任何一个:
x-lock(1,2); retain x-lock x-lock(2,3); update(2,3) to (2,5); retain x-lock x-lock(3,2); retain x-lock x-lock(4,3); update(4,3) to (4,5); retain x-lock x-lock(5,2); retain x-lock
那么,第二个UPDATE在试图获取任意行(因为第一个update锁住了所有的行)的锁时都会阻塞,知道第一个UPDATE操作提交或者回滚:
x-lock(1,2); block and wait for first UPDATE to commit or roll back
当使用READ COMMITTED级别时,第一个UPDATE操作获取排他锁,并且立即释放它不修改的行的锁:
x-lock(1,2); unlock(1,2) x-lock(2,3); update(2,3) to (2,5); retain x-lock x-lock(3,2); unlock(3,2) x-lock(4,3); update(4,3) to (4,5); retain x-lock x-lock(5,2); unlock(5,2)
对于第二个UPDATE,InnoDB会做"半一致性(semi-consistent)"读,它返回最近一次提交版本的记录给MySQL,然后MySQL会决定这个记录是否和UPDATE语句的WHERE条件匹配:
x-lock(1,2); update(1,2) to (1,4); retain x-lock x-lock(2,3); unlock(2,3) x-lock(3,2); update(3,2) to (3,4); retain x-lock x-lock(4,3); unlock(4,3) x-lock(5,2); update(5,2) to (5,4); retain x-lock
使用READ COMMITTED隔离级别的效果和被废弃的innodb_locks_unsafe_for_binlog配置项是一样的,除了以下两点:
- 设置innodb_locks_unsafe_for_binlog是一个全局的配置,会影响所有的会话。但隔离级别可以选择设置为对所有会话有效或者对单个会话有效。
- innodb_locks_unsafe_for_binlog只能在服务启动时的时候设置。但隔离级别可以在启动时和运行时设置。
-
READ UNCOMMITTED
SELECT操作使用不加锁的方式,不过有可能会返回较早的版本数据。因此使用这种级别时,读取是不一致的。这个也叫做脏读。除此之外,这个级别和READ COMMITTED级别一样。
-
SERIALIZABLE
这个级别和REPEATED READ级别类似,不同地方在于,如果autocommit没有设置,InnoDB会隐世的把SELECT转换为SELECT ... LOCK IN SHARE MODE。如果autocommit设置了,SELECT就是一个事务。It therefore is known to be read only and can be serialized if performed as a consistent (nonlocking) read and need not block for other transactions.(不会翻译-_-||)(如果想要在有其他事务修改数据时阻塞住当前[SELECT]操作,不要设置autocommit)