在MySQL并发事务导致的死锁中提到InnoDB
存储引擎默认事务隔离级别为 Repeatable Read
,在这种情况下,select 查询记录时,不会存在锁,除非显示的调用lock in share mode或者for update。本文来说一下为什么查询记录时不存在锁。
-
InnoDB事务回滚时,从哪里获取旧数据?
事务具有4个特性:原子性、一致性、隔离性和持久性。
事务的隔离性由锁实现,原子性、一致性和持久性通过数据库的redo log
和undo log
来实现。redo log
称为重做日志,用来保证事务的原子性和持久性,undo log
用来保证事务的一致性。
一致性是事务回滚时,数据还原为事务开始前的状态。 也就是说,undo log
中存放的是历史数据集,历史数据集中的每行数据也叫做快照数据。 -
InnoDB的一致性非锁定读
在
InnoDB
中,不加lock in share mode
和for update
的查询,都叫做普通查询,即查询数据时,数据行没有锁。这种读取数据的方式称之为一致性非锁定读。一致性非锁定读是指
InnoDB
存储引擎通过查询数据的历史数据作为查询的结果集,这里所说的历史数据即为undo log
中的历史数据。这种方式读取数据的好处在于:如果读取的行正在执行update
或delete
时,读取操作不会等待锁的释放,而是去读取一个快照数据,提高数据库的并发性。一行记录的历史数据可能有多个版本(例如:当前行可能多次执行update
,每次执行都是一个版本),称之为数据的多版本并发控制(Multi Version Concurrency Control,MVCC
),行数据的每个版本都称为一个快照数据。
在InnoDDB
存储引擎中,不同的事务隔离级别下,读取的方式不同,对快照数据的定义也不同。InnoDB中常用的事务隔离级别有Read Committed
和Repeatable Read
,它们都使用一致性非锁定读,但是它们对于快照数据的定义却不相同。select * from a ; +----+-----+ | id | num | +----+-----+ | 1 | 1 | +----+-----+
在
Read Committed
事务隔离级别下,对于快照数据,非一致性锁定读总是读取被锁定行的最新一份数据。例如:时间 会话A 会话B 会话C 1 begin; 2 update a set num = 2 where id = 1; 3 begin; 4 commit; 5 select num from a where id = 1;返回2 6 begin; 7 update a set num = 3 where id = 1; 8 commit; 9 select num from a where id = 1;返回3 10 commit; 在
InnoDB
默认的Repeatable Read
下,对于快照数据,非一致性锁定读总是读取一个事务开始时的行数据版本。例如:时间 会话A 会话B 会话C 1 begin; 2 update a set num = 2 where id = 1; 3 begin; 4 commit; 5 select num from a where id = 1;返回1 6 begin; 7 update a set num = 3 where id = 1; 8 commit; 9 select num from a where id = 1;返回1 10 commit; -
InnoDB的一致性锁定读
一致性锁定读即每次读取行数据时,对数据行加锁。例如:显示的调用lock in share mode或者for update。
select * from a where id = 1 lock in share mode; select * from a where id = 1 for update;
总结
- 事务回滚时,旧数据是从事务的
undo log
中获取 undo log
中的数据集称为快照数据集- 在
InnoDB
存储引擎中的查询不需要加锁,是因为使用的是一致性非锁定读,这也是为什么很多书使用锁查询数据时,调用lock in share mode
或for update
来实现数据的锁定。