文档版本:8.0
来源:transaction isolation levels
上一篇:InnoDB中的锁html
本篇主要介绍InnoDB的事务隔离级别。mysql
事务隔离是数据库发展的基础之一。隔离性(Isolation)是ACID中的I
;不一样的隔离级别用于在性能和多事务并行查询时的可靠性、一致性、再现性之间微调。
InnoDB完整实现了SQL:1992
标准中描述的四个隔离级别: 读未提交,读已提交,可重复读,序列化。InnoDB的默认级别是可重复读。
用户能够经过SET TRANSACTION
语句设置本会话或后续全部链接使用的隔离级别。若是要设置服务器针对全部链接的默认级别,在命令行或配置文件中使用--transaction-isolation
选项。关于隔离级别和设置级别的语法可在 Section 13.3.7, “SET TRANSACTION Statement” 中详细了解。
针对在此描述的这些隔离级别,InnoDB支持对它们使用不一样的加锁策略。例如,对于一些重要数据,须要严格遵循ACID,那么可使用默认级别可重复读以确保更高的一致性;相反地,对于批量报告数据,精确的一致性与可重放的结果显然没有最小化锁开销重要,那么可使用读已提交,甚至是读未提交来放宽一致性规则。序列化比可重复读更加严格,通常用于特殊场景,例如XA事务,或者排查并发与死锁问题。
下面列举MySQL是如何支持不一样的隔离级别的。按照使用频率从高到低依次排序。sql
可重复读是InnoDB的默认隔离级别。在这个级别下,同一事务内的快照读会使用第一次读取时生成的快照。这意味着若是你在同一事务内执行多条普通(无锁)SELECT
语句,这些语句的结果是一致的。详见Section 15.7.2.3, “Consistent Nonlocking Reads”。
对于加锁读(SELECT FOR SHARE
或 SELECT FOR UPDATE
),UPDATE
语句和DELETE
语句,加锁状况取决于语句是使用了惟一查询(使用了惟一索引),仍是使用了范围条件查询。数据库
在这个级别下,每一次快照读,甚至在同一事务内,都会设置一次新快照来读取。对于快照读,详见Section 15.7.2.3, “Consistent Nonlocking Reads”。
对于加锁读(SELECT FOR SHARE
或 SELECT FOR UPDATE
),UPDATE
语句和DELETE
语句,InnoDB只锁定索引值,不会锁住以前的间隙,所以其余事务能够在索引值旁进行插入。间隙锁只会用于外键约束检查和重复键检查。
由于间隙锁被禁用,其余会话能够在间隙中插入新行,因此可能会发生幻行。对于幻行,详见Section 15.7.4, “Phantom Rows”。
读已提交级别只支持数据行binlog。若是设置为混合模式,MySQL服务器会自动使用数据行binlog。
使用读已提交还会有如下影响:服务器
UPDATE
语句和DELETE
语句,InnoDB只会锁住将要更新或删除的行。在MySQL计算出WHERE条件后,不匹配行的行锁会被释放。这使得死锁发生的几率大大下降,但仍没法杜绝。UPDATE
语句,若是行已经被锁住,InnoDB会执行一个“半快照读”,返回最后提交的版本给MySQL从而决定WHERE条件将匹配哪些行。若是有匹配行(将必须被更新),MySQL再次读行而且此次InnoDB将会加锁或等待锁。下面举一个例子,先创建一张表:并发
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;
表没有索引,因此搜索和索引扫描将使用隐藏的聚簇索引,而不是索引列,来锁定行记录(详见Section 15.6.2.1, “Clustered and Secondary Indexes”)。
让一个会话用下列语句执行UPDATE:性能
# Session A START TRANSACTION; UPDATE t SET b = 5 WHERE b = 3;
接着让一个会话用下列语句执行UPDATE:命令行
# Session B UPDATE t SET b = 4 WHERE b = 2;
InnoDB执行各个UPDATE时,首先会得到各行的独占锁,而后决定是否修改行。若是InnoDB没有修改行,就释放锁;不然InnoDB在事务结束前会一直持有锁,因而事务流程会被这样影响:
当使用默认的可重复读隔离级别时,第一个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提交或回滚:code
x-lock(1,2); block and wait for first UPDATE to commit or roll back
但若是使用读已提交,第一个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执行一个“半快照读”,返回所读行最后提交的版本给MySQL,从而决定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
不过,若是WHERE条件包含索引列,而且InnoDB使用了这个索引,那么只会针对索引列进行获取、保持锁。在下面的例子中,第一个UPDATE会获取全部b=2的行的独占锁。第二个UPDATE在试图获取同一条记录的独占锁时被阻塞,而且也会使用b列索引。
CREATE TABLE t (a INT NOT NULL, b INT, c INT, INDEX (b)) ENGINE = InnoDB; INSERT INTO t VALUES (1,2,3),(2,2,4); COMMIT; # Session A START TRANSACTION; UPDATE t SET b = 3 WHERE b = 2 AND c = 3; # Session B UPDATE t SET b = 4 WHERE b = 2 AND c = 4;
读已提交隔离级别能够在MySQL启动时和运行中设置,若是在运行中设置,能够设置为全局生效,或单个会话生效。
SELECT语句将以无锁方式执行,但可能会使用行的旧版本数据。所以在这个级别下,读语句可能不一致,这也称做脏读。在其余方面,读未提交与读已提交表现相同。
这个级别与可重复读相似,但若是autocommit
被禁用,InnoDB会隐式地将全部普通SELECT语句转换为SELECT ... FOR SHARE
。若是autocommit
被开启,SELECT语句自成一条事务。所以它是只读的,而且在快照(无锁)读时可以被序列化,也不会被其余事务阻塞。(若是要使普通SELECT语句在其它事务修改了行后阻塞,禁用autocommit
。)