MySQL锁
本文字数:1.4k 字 | 阅读时长 ≈ 4 min

MySQL锁

本文字数:1.4k 字 | 阅读时长 ≈ 4 min

在开始介绍之前要明确一下:无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想。

乐观锁

乐观锁假设数据一般不会产生并发问题,在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测。

如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般实现乐观锁的方式是记录数据版本。

乐观并发控制相信事物之间的数据竞争的概率是比较小的,因此尽可能直接做下去,知道提交的时候才会去锁定,所以不会产生锁和死锁。

乐观锁实现方式
使用乐观锁不需要借助数据库的锁机制。主要依靠:冲突检测和数据更新。
通过一个单独的可以顺序递增的version字段。

乐观锁每次在执行数据的修改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改并对版本号执行+1操作,否则就执行失败。

除了version以外,还可以使用时间戳,因为时间戳天然具有顺序递增性。

悲观锁

当我们要对一个数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对数据进行加锁以防止并发。

这种借助数据库锁机制在修改数据之前先锁定,再修改的方式被称之为悲观并发控制。

但在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会。

悲观锁的实现机制,往往依靠数据库提供的锁机制。流程如下:

MySQL中InnoDB默认行级锁。行级锁基于索引,若一条SQL语句用不到索引是不会使用行级锁,会使用表级锁把整张表锁住。

高并发问题

一旦上高并发的时候,就只有一个线程可以修改成功,就会存在大量的失败。

对于类似淘宝这样的电商网站,高并发是常有的事情,让用户感知到失败显然不合理。需要想办法减少乐观锁的粒度。

例如修改商品库存:

update item set quantity=quantity - 1 where id = 1 and quantity - 1 > 0

以上SQL语句,若用户下单数为1,则通过quantity - 1的方式进行乐观锁控制。

共享锁称为读锁,简称S锁,指的是多个事务对同一个资源可以共享同一个锁,都可以访问到数据,但是只能读不能修改。

排它锁称为写锁,简称X锁,排他锁不能与其他锁并存。若一个事务获取了一个数据的排他锁,其他事务不能再获取该数据的其他锁,包括共享锁和排他锁。

MySQL中InnoDB引擎的修改语句中,UPDATE、DELETE、INSERT都会自动给涉及到的数据加上排他锁。SELECT语句默认不会添加任何锁类型,加排他锁可以使用select … for update语句,加共享锁使用select … lock in share mode语句。

加过排他锁的数据行在其他事务中是不能修改数据,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select … from …查询数据,因为普通查询没有任何锁机制。

MySQL四种事务隔离级

未提交读:允许脏读,可能读取到其它会话中未提交事务修改的数据。

提交读:只能读取到已经提交的数据。

可重复读:在同一个事务内的查询都是与事务开始时刻一致。

串行读:每次读都需要获取表级共享锁,读写相互阻塞。

脏读
当一个事务正在访问数据时,并且对数据进行了修改,而修改还未提交到数据库中。另一个事务也访问了这个数据,然后使用了该数据。

不可重复读
在一个事务中,多次读取同一数据。在该事务未结束时,另外一个事务也访问了该同一数据。在第一次事务中的两次读数据之间,由于第二个事务的修改,第一次事务两次读取到的数据可能不一样,因为称为不可重复读。

幻读
第一个事务对一个表的数据进行了修改,修改涉及表中的全部数据行。同时第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。以后就会发生操作第一个事务的用户发现表中还有未修改的数据行,就像发生了幻觉。

串行读
最高隔离机制,强制事务串行执行。

Dec 30, 2019