Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

scenario

In everyday work, there are often business requirements for precise control of quantity. For example, the inventory of goods, the number of prizes, the number of applicants, etc., these scenarios tend to have high concurrency. Take the inventory reduction scenario for example, if it is not well controlled, it is likely to be oversold.

Method 1: use file exclusive locking

The flock function is used to acquire a lock on a file that can only be acquired by one thread at a time. Other threads that do not acquire the lock will either block or fail to acquire it.

When obtaining the lock, first query the inventory, if the inventory is greater than 0, then place the order operation, reduce the inventory, and then release the lock.

Method 2: Use pessimistic locks

InnoDB storage engine supports row-level locking. When a row is locked, other processes cannot operate on the row.

In the InnoDB storage engine, when we execute an UPDATE statement, a row lock is automatically added to the row.


update goods set repertory = repertory - 1 WHERE goods_id = 1234 and repertory > 0

Copy the code

Another way to do this is to explicitly lock using for update, where two conditions are met:

  • Engine is the InnoDB

  • Actions need to be in a transaction (begin/ COMMIT)


begin;

select repertory from goods where goods_id = 1234 for update;

if (repertory > 0) {

update goods set repertory = repertory - 1 where goods_id = 1234;

}

commit;

Copy the code

Pessimistic locks use the “lock first, access later” strategy, but this strategy can increase the burden of the database and may lead to deadlocks. In practice, pessimistic locking is not used for high concurrency scenarios, because when a transaction locks a record, all other transactions will occur, and massive transaction blocking can bring down the entire system.

Method 3: Use optimistic locks

Optimistic locking is always assuming the best case, every time I go to get the data, I think others will not modify it, so I will not lock it, but I will judge whether others have updated the data during the update period, which can be achieved by using the version number mechanism and CAS algorithm.

Let’s take the version number method as an example.

In the version number mechanism, a version field is added to the data table to indicate the number of times the data has been modified. When the data has been modified, the version value will be increased by one. When thread A wants to update the data value, it also reads the version value. When submitting the update, it updates the version value only when the version value it just read is the same as the version value in the current database. Otherwise, the update operation is retried until the update succeeds.


select version as old_version from goods where goods_id = 1234;

update goods set repertory = repertory - 1, version = version + 1 where goods_id = 1234 and repertory = 0 and version = ${version};

Copy the code

For example, if version = 1 and version = 1, all 6 requests come in and execute the above statement. If version = 1 is found, there must be one line that succeeds first and version = 2. If another update statement finds that version is not the same as the version in the previous select, it indicates that the version has been changed by another request and the update will be abandoned.

Method 4: Use queues

We can solve the high concurrency problem by using the Redis queue scheme, where we use a queue to receive requests and pop data out for processing one by one.

We generate a unique ID using the order number, user ID, item ID, purchase quantity, and so on, and then push the value to the order queue and start another thread to consume the data.


lpush repertory orderId_goodsId_accountId_count

Copy the code

Method 5: Use Redis

In the case of high concurrency, frequent library reads and writes can cause serious performance problems. We can use Redis.

Redis operations are atomic, you can write inventory of goods into Redis. DECR operation is performed on the inventory before placing an order. If the return result is >=0, the inventory can be placed and reduced normally. Otherwise, the inventory is insufficient.


172.18.4.181:7006> set repertory 2

OK

172.18.4.181:7006> DECR repertory

(integer) 1

172.18.4.181:7006> DECR repertory

(integer) 0

172.18.4.181:7006> DECR repertory

(integer) -1

Copy the code

Reference documentation

  • Analysis of several realization methods to solve concurrency problem in PHP

  • Essential optimism and pessimism locks for interviews