第十章:第17节 MySQL进阶篇——快照读与当前读

更新于:2017-09-01 21:28:54

在InnoDB表,我们知道了对数据的读是分为两种的——“普通读”和“加锁读”。


普通读”由于不加锁,它既不影响其他事务加锁,其他事务的锁也影响不到它,对MySQL的并发没太大影响。我们平时读数据在不是很严谨的情况下,用“普通读”即可。


比如更新一篇文章的阅读量,用“普通读”获取已阅读数,再update加一。没必要加锁读取,只是做个显示,就算有误差也不会影响什么。


加锁读”加锁,会影响其他事务加锁,影响MySQL的并发性。对数据要求很严谨的情况下,用“加锁读”。


比如抢购功能,如果是用MySQL实现这个功能。MySQL表中有个字段记录商品库存,我们写的程序肯定是先判断有没有库存(select),如果有,就更新库存减一(update),在订单表添加一条记录(insert)。此时的select就要加“排他锁”,只能是事务一个一个地去执行 select+update+insert ,这样就不会出现数据出错。


如果不加“排他锁”,A事务select库存是1的话,还没有完成update设置为0操作时,此时B事务select库存也是1,然后B事务等着加“排他锁”。A事务结束,B事务抢到“排他锁”,此时库存已为0,然后B事务又把库存执行了一次更新为0的操作,在订单表插入一条订单记录。


如果是这样的话,程序就出错了,多卖出一件商品。


在“RR”隔离级别下,我们做演示,同样的select语句,尤其数据被其他事务修改了,“普通读”和“加锁读”数据是不一样的,这是怎么回事呢?


在数据库中,有一门技术可以让数据库提供同一数据的多个版本,这种技术叫做数据多版本并发控制(MultiVersion Concurrency Control,简称MVCC或MCC),也经常称为多版本数据库。


MVCC最大的好处:读不加锁,读写不冲突。在读多写少的应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能。


在MVCC并发控制中,“普通读”读取的是记录的可见版本 (有可能是历史版本),不用加锁,我们可以叫它“快照读(snapshot read)”。“加锁读”读取的是记录的最新版本,当前读返回的记录都会加上锁,保证其他事务不会再并发修改这条记录,我们可以叫它“当前读 (current read)”。


一条普通的select语句就是“快照读”,如 select * from table where ?;


我们来仔细解读下“当前读”都有哪些:


  • select * from table where ? lock in share mode;

  • select * from table where ? for update;

  • insert into table values (…);

  • update table set ? where ?;

  • delete from table where ?;


所有以上的语句,都属于当前读,读取记录的最新版本。并且,读取之后,还需要保证其他并发事务不能修改当前记录,对读取记录加锁。其中,除了第一条语句,对读取记录加S锁 (共享锁)外,其他的操作,都加的是X锁 (排它锁)。


为什么将 插入/更新/删除 操作,也被归为当前读?可以看看下面这个 更新 操作,在数据库中的执行流程:

800.jpg


从图中,可以看到,一个Update操作的具体流程。当Update SQL被发给MySQL后,MySQL Server会根据where条件,读取第一条满足条件的记录,然后InnoDB引擎会将第一条记录返回,并加锁 (current read)。待MySQL Server收到这条加锁的记录之后,会再发起一个Update请求,更新这条记录。一条记录操作完成,再读取下一条记录,直至没有满足条件的记录为止。因此,Update操作内部,就包含了一个当前读。同理,Delete操作也一样。Insert操作会稍微有些不同,简单来说,就是Insert操作可能会触发Unique Key的冲突检查,也会进行一个当前读。

 

注:根据上图的交互,针对一条当前读的SQL语句,InnoDB与MySQL Server的交互,是一条一条进行的,因此,加锁也是一条一条进行的。先对一条满足条件的记录加锁,返回给MySQL Server,做一些DML操作;然后在读取下一条加锁,直至读取完毕。