在开始介绍之前要明确一下:无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想。
乐观锁
乐观锁假设数据一般不会产生并发问题,在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测。
如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。
在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般实现乐观锁的方式是记录数据版本。
乐观并发控制相信事物之间的数据竞争的概率是比较小的,因此尽可能直接做下去,知道提交的时候才会去锁定,所以不会产生锁和死锁。
乐观锁实现方式
使用乐观锁不需要借助数据库的锁机制。主要依靠:冲突检测和数据更新。
通过一个单独的可以顺序递增的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四种事务隔离级
未提交读:允许脏读,可能读取到其它会话中未提交事务修改的数据。
提交读:只能读取到已经提交的数据。
可重复读:在同一个事务内的查询都是与事务开始时刻一致。
串行读:每次读都需要获取表级共享锁,读写相互阻塞。
脏读
当一个事务正在访问数据时,并且对数据进行了修改,而修改还未提交到数据库中。另一个事务也访问了这个数据,然后使用了该数据。
不可重复读
在一个事务中,多次读取同一数据。在该事务未结束时,另外一个事务也访问了该同一数据。在第一次事务中的两次读数据之间,由于第二个事务的修改,第一次事务两次读取到的数据可能不一样,因为称为不可重复读。
幻读
第一个事务对一个表的数据进行了修改,修改涉及表中的全部数据行。同时第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。以后就会发生操作第一个事务的用户发现表中还有未修改的数据行,就像发生了幻觉。
串行读
最高隔离机制,强制事务串行执行。
本文由 小小小气鬼 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Dec 30, 2019 10:23 am