MySQL查询不需要锁?InnoDB的非一致性锁定读

在MySQL并发事务导致的死锁中提到InnoDB存储引擎默认事务隔离级别为 Repeatable Read,在这种情况下,select 查询记录时,不会存在锁,除非显示的调用lock in share mode或者for update。本文来说一下为什么查询记录时不存在锁。

  • InnoDB事务回滚时,从哪里获取旧数据?

    事务具有4个特性:原子性、一致性、隔离性和持久性。
    事务的隔离性由锁实现,原子性、一致性和持久性通过数据库的redo logundo log来实现。redo log称为重做日志,用来保证事务的原子性和持久性,undo log用来保证事务的一致性。
    一致性是事务回滚时,数据还原为事务开始前的状态。 也就是说,undo log中存放的是历史数据集,历史数据集中的每行数据也叫做快照数据

  • InnoDB的一致性非锁定读

    InnoDB中,不加lock in share modefor update的查询,都叫做普通查询,即查询数据时,数据行没有锁。这种读取数据的方式称之为一致性非锁定读

    一致性非锁定读是指InnoDB存储引擎通过查询数据的历史数据作为查询的结果集,这里所说的历史数据即为undo log中的历史数据。这种方式读取数据的好处在于:如果读取的行正在执行updatedelete时,读取操作不会等待锁的释放,而是去读取一个快照数据,提高数据库的并发性。一行记录的历史数据可能有多个版本(例如:当前行可能多次执行update,每次执行都是一个版本),称之为数据的多版本并发控制(Multi Version Concurrency Control,MVCC),行数据的每个版本都称为一个快照数据
    InnoDDB存储引擎中,不同的事务隔离级别下,读取的方式不同,对快照数据的定义也不同。InnoDB中常用的事务隔离级别有Read CommittedRepeatable 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;
    

总结

  1. 事务回滚时,旧数据是从事务的undo log中获取
  2. undo log中的数据集称为快照数据集
  3. InnoDB存储引擎中的查询不需要加锁,是因为使用的是一致性非锁定读,这也是为什么很多书使用锁查询数据时,调用lock in share modefor update来实现数据的锁定。

你可能感兴趣的:(mysql)