什么是一致性读取(consistent read)
一致性读取是
InnoDB
在READ COMMITTED
隔离级别中处理SELECT
语句的默认模式。一致性读取不会在它访问的 table 上设置任何锁。
一种读操作,它使用快照 信息来呈现基于某个时间点的查询结果,而不管同时运行的其他事务的更改。如果查询到的数据被其他事务更改,则根据undo log 的内容重建原始数据。此技术通过强制事务等待其他事务完成来避免一些可以减少并发 性的锁定问题。
一致的非锁定读取(Consistent Nonlocking Reads)
一致性读取 意味着 InnoDB
使用多版本控制(mvcc)在某个时间点向查询显示数据库的快照。查询将看到在该时间点之前提交的事务所做的更改,而不会看到 later 或未提交的事务所做的更改。
此规则有个特殊情况:如果在事务中更新了某些行, 则将看到已更新行的最新版本,也能看到其他事务已经提交的版本。
READ COMMITTED
读已提交,事务中的每个一致读取都会设置和读取自己的新快照。
REPEATABLE READ
可重复读,则同一事务中的所有一致读取都将读取该事务中第一个读取建立的快照。
下面这张图相信大家都很熟悉了,现在知道
REPEATABLE READ
为啥没有 不可重复读的问题了吧,因为每次都是读取的第一次建立的快照。
注意
-
数据库状态的快照适用于事务中的
SELECT
语句,而不一定适用于DML 语句。如果插入 或修改 某些行,然后提交该事务,则从另一个并发
REPEATABLE READ
事务发出的DELETE
或UPDATE
语句可能会影响这些刚刚提交的行,即使会话无法查询它们,也能更新和删除确实可见的,更新之后,查询也能查询到这些数据了。例如,您可能会遇到如下情况:SQLSELECT COUNT(c1) FROM t1 WHERE c1 = 'xyz'; -- Returns 0: no rows match. DELETE FROM t1 WHERE c1 = 'xyz'; -- 上面统计没有数据,删除可能删除多行数据(其他事务提交的数据) SELECT COUNT(c2) FROM t1 WHERE c2 = 'abc'; -- Returns 0: no rows match. UPDATE t1 SET c2 = 'cba' WHERE c2 = 'abc'; -- Affects 10 rows: 把其他事务的数据更新到了 SELECT COUNT(c2) FROM t1 WHERE c2 = 'cba'; -- Returns 10: 再次查询,快照已经被更新
-
一致性读取不适用于某些 DDL 语句:
- 一致性读取不适用于
DROP TABLE
,因为 MySQL 无法使用已删除的表,并且InnoDB
会销毁该表。 ALTER TABLE
时,在事务中重新发起一致性读,会返回错误:"Table definition has changed, please retrytransaction"
- 一致性读取不适用于
锁定读取(Locking Reads )
先查询数据,然后根据查询结果在同一事务中(A)插入或更新相关数据。其他事务可能(A事务开启后查询前)在更新或删除您刚刚查询的相同行。再去更新可能就会有问题了。
这时候就需要用锁定读取了: select ... for share 、select ... for update
select...for share
在读取的任何行上设置共享锁 。其他会话可以读取这些行(不会阻塞其他for share),但在您的事务提交之前无法修改它们。如果for share读取前这些行中的任何一行被另一个尚未提交的事务更改(或者 for update
),当前select...for share
将等待该事务结束,然后使用最新的值。
select...for update
对于索引记录,搜索会锁定行和任何关联的索引条目,就像为这些行发出UPDATE
语句一样。其他事务被阻止更新这些行, 同时当前for update
事务未提交,则 其他的锁读(for share or for update) 或者 update 将被阻塞,直到事务提交。
锁读应用场景
-
给组织添加成员(
for share
)添加成员时,首先确保组织存在,或者组织状态允许成员的加入
SQL--开启事务,锁定读取 SELECT * FROM parent_dept WHERE id =1 and state = '1' FOR SHARE; -- 校验 parent_dept 数据 ,条件满足则插入数据 insert into person values (1,'小刘');
不加锁读的话,可能刚读取完成,就被其他事务 删除 或者更新了。
-
给组织添加成员(
for update
)添加成员时,不仅仅需要组织存在或者状态,现在我还要维护组织表的成员人数。
SQL--开启事务,锁定读取 SELECT num FROM parent_dept WHERE id =1 and state = '1' FOR update; -- 校验 parent_dept 数据 ,条件满足则插入数据 insert into person values (1,'小刘'); -- 人数加1 update parent_dept set num = num+1 ;
上面如果用
for share
的话,那么并发事务到了update
时候就会形成死锁 ,这时候用for update
就避免这个问题了
注意
子查询没有指定的话,子查询是不会加锁的,如下t2表对应的行不会加锁:
sql
SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2) FOR UPDATE;
如要对子查询页加锁:
sql
SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2 FOR UPDATE) FOR UPDATE;
总结
本篇文章讲了一致性读取的相关概念,同时分享了锁读(共享锁、排他锁)的 应用场景,以及一些注意事项。如果有用感谢各位老铁一键三连!!!
最后:加锁的时候需要注意,如果where 条件没有索引,会锁表
mysql 官方文档:dev.mysql.com/doc/refman/...