banner
RandyChan

RandyChan

深漂 / Back-end developer
github

浅析悲观锁和乐观锁

在关系型数据库中,悲观并发控制(悲观锁)和乐观并发控制(乐观锁)是资源并发控制主要采用的解决方案。

无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想。其实不仅仅是关系型数据库系统中有乐观锁和悲观锁的概念,像 memcache、hibernate、tair 等都有类似的概念。

针对于不同的业务场景,应该选用不同的并发控制方式。更不要把他们和数据中提供的锁机制(行锁、表锁、排他锁、共享锁)混为一谈。

下面来分别了解一下悲观锁和乐观锁,先定义一个商品表简单表设计如下:

CREATE TABLE `products` (
	`id` INT unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
	`quantity` INT unsigned COMMENT '库存',
	PRIMARY KEY (`id`)
) ENGINE=InnoDB;

悲观锁#

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

这种借助数据库锁机制在修改数据之前先锁定,再修改的方式被称之为悲观并发控制(又名 “悲观锁”,Pessimistic Concurrency Control,缩写 “PCC”)。

这种设计采用了 “一锁二查三更新” 模式,就是采用数据库中自带 select ... for update 关键字进行对当前事务添加行级锁先将要操作的数据进行锁上,之后执行对应查询数据并执行更新操作。

BEGIN;
SELECT quantity FROM products WHERE id = 1 FOR UPDATE;
UPDATE products SET quantity = quantity - 1 WHERE id = 1; 
COMMIT;

在 MySQL 数据库中的是 InnoDB 默认是行级锁。行级锁都是基于索引的,如果一条 SQL 语句没有命中索引是不会使用行级锁的,会把整张表锁住。所以在 select ... for update 中尽量缩小查询范围并命中索引。

悲观并发控制主要用于数据竞争激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。

乐观锁#

乐观锁( Optimistic Concurrency Control,缩写 “OCC” ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。数据版本可以通过版本号或者时间戳来实现。

SELECT quantity, version FROM products WHERE id = 1;
UPDATE products SET quantity = quantity - 1, version = version + 1
 WHERE id = 1 AND version = 原版本号;

使用版本号时,可以在数据初始化时指定一个版本号,每次对数据的更新操作都对版本号执行 + 1 操作。并判断当前版本号是不是该数据的最新的版本号。

但是使用版本号对于并发高网站来说,只有一个线程可以成功修改数据,会出现很多业务报错,对用户来说很不友好。

所以我们可以根据业务情况来减小乐观锁力度,最大程度的提升吞吐率,提高并发能力。比如下单商品业务只需要商品有库存就能下单.

UPDATE products SET quantity = quantity - 1 WHERE id = 1 AND quantity - 1 > 0;

乐观并发控制多数用于数据竞争较少的环境,这种环境中,偶尔回滚事务的成本会低于读取数据时锁定数据的成本,因此可以获得比其他并发控制方法更高的吞吐量。

简述区别#

  • 乐观锁并未真正加锁,效率高。一旦锁的粒度掌握不好,更新失败的概率就会比较高,容易发生业务失败。

  • 悲观锁依赖数据库锁,效率低。更新失败的概率比较低。

参考链接#

谢致#

感谢你的阅读,让我们一起探索知识,共同成长。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。