Pessimistic and optimistic locking of mysql database

Pessimistic Concurrency Control

When we want to modify a piece of data in a database, in order to avoid being modified by others at the same time, the best way is to directly lock the data to prevent concurrency.

This way of using the database lock mechanism to lock the data before modifying it and then modify it is called pessimistic concurrency control (also known as "pessimistic lock", Pessimistic Concurrency Control, abbreviated "PCC").

It is called pessimistic lock because it is a concurrency control method with a pessimistic attitude towards data modification. We generally think that the probability of data being modified concurrently is relatively high, so we need to lock it before modification.

Pessimistic concurrency control is actually a conservative strategy of "getting the lock before access", which provides a guarantee for the safety of data processing.


But in terms of efficiency, the mechanism of processing locks will cause additional overhead to the database and increase the chance of deadlock;

In addition, it will reduce parallelism. If a transaction locks a row of data, other transactions must wait for the transaction to complete before processing that row of data.

Optimistic Locking

Optimistic Locking (Optimistic Locking) is relatively pessimistic locking. Optimistic locking assumes that data will not cause conflicts under normal circumstances. Therefore, when the data is submitted and updated, it will formally detect whether the data conflicts or not. If conflicts are found If the error occurs, let the user return the wrong information and let the user decide how to do it.

Compared with pessimistic locking, optimistic locking does not use the locking mechanism provided by the database when processing the database.

The general way to achieve optimistic locking is to record the data version.

Optimistic concurrency control believes that the probability of data races between transactions is relatively small, so do it as directly as possible, and do not lock until commit, so no locks and deadlocks will occur.

Pessimistic lock implementation

The realization of pessimistic locking often relies on the locking mechanism provided by the database. In the database, the process of pessimistic locking is as follows:

Before modifying the record, try to add exclusive locking to the record.

If the lock fails, indicating that the record is being modified, the current query may have to wait or throw an exception. The specific response method is determined by the developer according to actual needs.

If the lock is successfully secured, the record can be modified and the transaction will be unlocked after the transaction is completed.
In the meantime, if other transactions lock the record, they must wait for the current transaction to unlock or throw an exception directly.

Let's take the more commonly used MySql Innodb engine as an example to illustrate how to use pessimistic locks in SQL.

Note: To use pessimistic locking, we must turn off the auto-commit attribute in the mysql database set autocommit=0. Because MySQL uses autocommit mode by default, that is to say, when you perform an update operation, MySQL will immediately submit the result.

Let's take a simple example, such as the need to deduct inventory during Taobao's ordering process to illustrate how to use pessimistic lock:

//0.开始事务
begin; 
//1.查询出商品库存信息
select quantity from items where id=1 for update;
//2.修改商品库存为2
update items set quantity=2 where id = 1;
//3.提交事务
commit;

Above, before modifying the record with id = 1, lock it first through for update, and then modify it. This is a typical pessimistic locking strategy.

If the code for modifying the inventory above occurs concurrently, only one thread can open the transaction at the same time and obtain the lock with id=1, and other transactions must wait for the transaction to be submitted before execution. In this way, we can ensure that the current data will not be modified by other transactions.

As we mentioned above, using select...for update will lock the data, but we need to pay attention to some lock levels, MySQL InnoDB default row-level lock. Row-level locks are based on indexes. If an SQL statement does not use an index, row-level locks will not be used. Table-level locks will be used to lock the entire table. This requires attention.

Optimistic lock implementation

The use of optimistic locking does not require the use of database locking mechanism.

In fact, the concept of optimistic locking has already explained its specific implementation details: mainly two steps: conflict detection and data update. One of its implementation methods is more typical: Compare and Swap (CAS) technology.

CAS is an optimistic locking technology. When multiple threads try to use CAS to update the same variable at the same time, only one thread can update the value of the variable, and other threads fail. The failed thread will not be suspended, but will be Inform you that you have failed in this competition and you can try again.

For example, the previous inventory deduction problem can be achieved through optimistic locking as follows:

//查询出商品库存信息: quantity = 3
select quantity from items where id=1
//修改商品库存为2
update items set quantity=2 where id=1 and quantity = 3;

Above, before updating, we first query the current inventory number (quantity) in the inventory table, and then use the inventory number as a modification condition when doing the update.

当我们提交更新的时候,判断数据库表对应记录的当前库存数与第一次取出来的库存数进行比对,如果数据库表当前库存数与第一次取出来的库存数相等,则予以更新,否则认为是过期数据。

ABA问题与version控制

但是以上更新语句存在一个比较重要的问题,即ABA问题。

比如说一个线程1从数据库中取出库存数3,这时候另一个线程2也从数据库中库存数3,并且线程2进行了一些操作将库存数变成了2,紧接着又将库存数变成3,这时候线程1进行CAS操作发现数据库中仍然是3,然后线程1操作成功。尽管线程1的CAS操作成功,但是不代表这个过程就是没有问题的。

有一个比较好的办法可以解决ABA问题,那就是通过一个单独的可以顺序递增的version字段。改为以下方式即可:

//查询出商品信息,version = 1
select version from items where id=1
//修改商品库存为2
update items set quantity=2,version = 3 where id=1 and version = 2;

乐观锁每次在执行数据的修改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行+1操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现ABA问题,因为版本号只会增加不会减少。

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

乐观锁在数据库上的实现完全是逻辑的,数据库本身不提供支持,而是需要开发者自己来实现。

乐观锁实现总结

常见的做法有两种:版本号控制及时间戳控制。

版本号控制的原理:

为表中加一个 version 字段;
当读取数据时,连同这个 version 字段一起读出;
数据每更新一次就将此值加一;
当提交更新时,判断数据库表中对应记录的当前版本号是否与之前取出来的版本号一致,如果一致则可以直接更新,如果不一致则表示是过期数据需要重试或者做其它操作(PS:这完完全全就是 CAS 的实现逻辑呀~)
至于时间戳控制,其原理和版本号控制差不多,也是在表中添加一个 timestamp 的时间戳字段,然后提交更新时判断数据库中对应记录的当前时间戳是否与之前取出来的时间戳一致,一致就更新,不一致就重试。

特点

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

高并发问题

以上SQL其实还是有一定的问题的,就是一旦高并发的时候,就只有一个线程可以修改成功,那么就会存在大量的失败。

对于像淘宝这样的电商网站,高并发是常有的事,总让用户感知到失败显然是不合理的。所以,还是要想办法减小乐观锁的粒度的。

有一条比较好的建议,可以减小乐观锁力度,最大程度的提升吞吐率,提高并发能力!如下:

//修改商品库存
update item 
set quantity=quantity - 1 
where id = 1 and quantity - 1 > 0

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

以上 update 语句,在执行过程中,会在一次原子操作中自己查询一遍quantity 的值,并将其扣减掉1。

高并发环境下锁粒度把控是一门重要的学问,选择一个好的锁,在保证数据安全的情况下,可以大大提升吞吐率,进而提升性能。

如何选择?

在乐观锁与悲观锁的选择上面,主要看下两者的区别以及适用场景就可以了。

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

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

随着互联网三高架构(高并发、高性能、高可用)的提出,悲观锁已经越来越少的被使用到生产环境中了,尤其是并发量比较大的业务场景。

参考资料

https://www.cnblogs.com/kyoner/p/11318979.html
https://www.cnblogs.com/nangec/p/12017881.html

Guess you like

Origin blog.csdn.net/universsky2015/article/details/108846698
Recommended