在MySQL的innodb底层中,对于select操作通常有两种实现方式,分别是:
非锁定一致性读
这种思路通常是体现在MySQL进行读取行记录的时候,如果该行处于被delete或者update访问的锁定状态的话(innodb中,默认在执行写操作的时候,会加入行锁),它会去读取这条记录的快照数据,从而减少受到锁的影响,目的是为了提升读性能。
之所以我们称之为非锁定状态,是因为当我们发送读请求的时候,并不需要等待相关行的排它锁进行释放。这里的读取数据源是来自于undo log中的版本记录数据。
下边我们结合一个案例来和大家进行分析:
假设我们有事务1和普通会话1,如下图:(t_user表中的user_id是主键索引)
在会话2中,存在一个事务逻辑,我们的会话1发起了select读操作,然而会话2创建的另一个事务,并且在会话1发起select之前执行了update操作。此时按理说会话2没有提交之前,这条1001的记录一直都会被排他锁锁住。
但是innodb默认的select请求,不会受到这些锁的影响,它们读取当被锁定记录的最新的一条历史版本数据(来自undo log中)
但是假设我们的会话1是带有事务的话,那么逻辑就会稍微有些变化,见下图:
此时,两种会话里面都会带有事务,在这种场景下,我们就需要结合事务的隔离级别进行分析了:
- Read Repeated隔离级别
在RR隔离级别下,select请求也是利用了Mvcc机制去读取历史数据,只不过是要到undo log中读取和当前事务版本号一致的记录。
- Read Commited隔离级别
在RC隔离级别下,select请求也是利用了Mvcc机制去读取历史数据,它会读取当前undo log中记录的版本号最新的一条记录内容。
看到这里,你可能会感觉,是不是我们的代码在进行select操作的时候,就完全不用担心被其他写操作影响导致锁住了呢?这里我只能说大部分情况下,select不会受到其他写操作的影响。但是如果我们在MySQL服务端执行了Lock Table类型的锁表语句,那么所有对被锁表的select查询就必须进入堵塞等待状态了。
锁定一致性读
通常锁定一致性读会发生在使用了for update 或者lock in share mode相关关键字的查询sql中。它的原理是锁定当前最新的行记录。
默认情况下,innodb存储引擎的select操作使用一致性非锁定读,但是在某些情况下,需要对读操作进行加锁以保证数据逻辑的一致性。Innodb存储引擎对select语句支持2种一致性锁定读(locking read)操作;
- SELECT ... FOR UPDATE
对于读取的行记录加一个X排它锁,其他事务不能对锁定的行加任何锁。
- SELECT ... LOCK IN SHARE MODE
对于读取的行记录添加一个S共享锁。其它事务可以向被锁定的行加S锁,但是不允许添加X锁,否则会被阻塞。
对于上述的两种锁来说,只要一旦对某个行访问的时候加上了,那么任何其他的写操作都不能访问该行的记录。