In a multi-user environment, multiple users may be updating the same records at the same time, which can create conflicts. This is the famous concurrency problem.
Typical conflicts are:
- Lost update : The update of one transaction overwrites the update result of other transactions, which is the so-called lost update. For example: User A changes the value from 6 to 2, and User B changes the value from 2 to 6, then User A loses his update.
- Dirty Reads : Dirty reads occur when a transaction reads other records that are halfway through the transaction. For example, the value seen by user A and B is 6, user B changes the value to 2, and the value read by user A is still 6.
In order to solve the problems caused by these concurrency. We need to introduce concurrency control mechanism.
Concurrency Control Mechanism
Pessimistic locking: Assuming that concurrency conflicts will occur, block all operations that may violate data integrity. [1]
Optimistic locking: Assuming that no concurrency conflicts will occur , only checks for data integrity violations when committing an operation. [1] Optimistic locking cannot solve the problem of dirty reads.
optimistic locking application
Optimistic lock introduction:
Optimistic Locking Compared with pessimistic locking , optimistic locking assumes that data will not cause conflict in general, so when the data is submitted and updated, the data conflict will be formally detected. If a conflict is found , then let the user return the wrong information, let the user decide what to do. So how do we implement optimistic locking? Generally speaking, there are two ways:
1. Implemented using the data version (Version) recording mechanism, which is the most commonly used implementation of optimistic locking. What is a data version? That is, adding a version identifier to the data is generally realized by adding a numeric "version" field to the database table. When reading data, read the value of the version field together, and add one to the version value every time the data is updated. When we submit the update, compare the current version information of the corresponding record in the database table with the version value taken out for the first time. If the current version number of the database table is equal to the version value taken out for the first time, it will be updated. Otherwise, it is considered to be expired data. Use the following picture to illustrate:
As shown in the figure above, if the update operations are performed sequentially, the version of the data will be incremented in sequence, and there will be no conflict. However, if there are different business operations that modify the same version of the data, the operation submitted first (B in the figure) will update the data version to 2. When A submits the update after B, it finds that the version of the data has been modified. , then the update operation of A will fail.
2. The second implementation method of optimistic locking is similar to the first one. It also adds a field to the table that needs optimistic locking control. The name does not matter. The field type uses timestamp , which is similar to the above version. When the update is submitted, check the timestamp of the data in the current database and compare it with the timestamp obtained before the update. If it is consistent, it is OK, otherwise it is a version conflict.
Example of use : Take MySQL InnoDB as an example
Take the previous example as an example: there is a field status in the goods table. If the status is 1, it means that the product has not been ordered. If the status is 2, it means that the product has been ordered. Then we must ensure that the product has been ordered when we place an order for the product. status is 1. Suppose the id of the item is 1.
The ordering operation includes 3 steps:
1. Check out the product information
select (status,status,version) from t_goods where id=#{id}
2. Generate orders based on product information
3. Modify the product status to 2
update t_goods
set status=2,version=version+1
where id=#{id} and version=#{version};
Then in order to use optimistic locking, we first modify the t_goods table, add a version field, and the default version value of the data is 1.
The initial data of the t_goods table is as follows:
For the implementation of optimistic locking, I use MyBatis to practice , as follows:
Goods entity class:
/** * ClassName: Goods <br/> * Function: Commodity entity. <br/>*/ public class Goods implements Serializable { /** * serialVersionUID: Serialization ID. */ private static final long serialVersionUID = 6803791908148880587L; /** * id: primary key id. */ private int id; /** * status: Product status: 1 has not placed an order, 2 has placed an order. */ private int status; /** * name: product name. */ private String name; /** * version: product data version number. */ private int version; @Override public String toString(){ return "good id:"+id+",goods status:"+status+",goods name:"+name+",goods version:"+version; } //setter and getter }
GoodsDao
/** * updateGoodsUseCAS: Use CAS (Compare and set) to update product information * @param goods commodity object * The number of lines affected by @return */ int updateGoodsUseCAS(Goods goods);
mapper.xml
<update id="updateGoodsUseCAS" parameterType="Goods"> <![CDATA[ update t_goods set status=#{status},name=#{name},version=version+1 where id=#{id} and version=#{version} ]]> </update>
GoodsDaoTest test class
@Test public void goodsDaoTest(){ int goodsId = 1; //Query the product information according to the same id and assign it to 2 objects Goods goods1 = this.goodsDao.getGoodsById(goodsId); Goods goods2 = this.goodsDao.getGoodsById(goodsId); //Print the current product information System.out.println(goods1); System.out.println(goods2); //Update product information 1 goods1.setStatus(2);//Modify status to 2 int updateResult1 = this.goodsDao.updateGoodsUseCAS(goods1); System.out.println("Modify product information 1"+(updateResult1==1?"Success":"Failure")); //Update product information 2 goods1.setStatus(2);//Modify status to 2 int updateResult2 = this.goodsDao.updateGoodsUseCAS(goods1); System.out.println("Modify product information 2"+(updateResult2==1?"Success":"Failure")); }
Output result:
good id:1,goods status:1,goods name:道具,goods version:1 good id:1,goods status:1,goods name:道具,goods version:1 Modification of product information 1 succeeded Failed to modify product information 2
illustrate:
In the GoodsDaoTest test method, we simultaneously find out the data of the same version, assign them to different goods objects, and then modify the good1 object first and then perform the update operation, which is successful. Then we modify goods2, and the operation fails when the update operation is performed. At this time, the data in the t_goods table is as follows:
mysql> select * from t_goods; +----+--------+------+---------+ | id | status | name | version | +----+--------+------+---------+ | 1 | 2 | Props | 2 | | 2 | 2 | Equipment | 2 | +----+--------+------+---------+ 2 rows in set mysql>
We can see that the data version with id 1 has been modified to 2 during the first update. So when we update good2, the update where condition does not match, so the update will not succeed. The specific sql is as follows:
update t_goods set status=2,version=version+1 where id=#{id} and version=#{version};
In this way we achieve optimistic locking
Pessimistic Lock Application
Need to use the database lock mechanism, such as SQL SERVER's TABLOCKX (exclusive table lock) When this option is selected, SQL Server will set an exclusive lock on the entire table until the end of the command or transaction. This will prevent other processes from reading or modifying the data in the table.
Used in SqlServer
Begin Tran
select top 1 @TrainNo=T_NO
from Train_ticket with (UPDLOCK) where S_Flag=0
update Train_ticket
set T_Name=user,
T_Time=getdate(),
S_Flag=1
where T_NO=@TrainNo
commit
We use the with (UPDLOCK) option when querying. When querying records, we add an update lock to the record, indicating that we are about to update this record. Note that update locks and shared locks do not conflict, that is Other users can also query the contents of this table, but it conflicts with update locks and exclusive locks. So other update users will block.
in conclusion
In the actual production environment, if the amount of concurrency is not large and dirty reading is not allowed, pessimistic locking can be used to solve the concurrency problem; but if the concurrency of the system is very large, pessimistic locking will bring very big performance problems, so we have to choose A method for optimistic locking.
In a multi-user environment, multiple users may be updating the same records at the same time, which can create conflicts. This is the famous concurrency problem.
Typical conflicts are:
- Lost update : The update of one transaction overwrites the update result of other transactions, which is the so-called lost update. For example: User A changes the value from 6 to 2, and User B changes the value from 2 to 6, then User A loses his update.
- Dirty Reads : Dirty reads occur when a transaction reads other records that are halfway through the transaction. For example, the value seen by user A and B is 6, user B changes the value to 2, and the value read by user A is still 6.
In order to solve the problems caused by these concurrency. We need to introduce concurrency control mechanism.
Concurrency Control Mechanism
Pessimistic locking: Assuming that concurrency conflicts will occur, block all operations that may violate data integrity. [1]
Optimistic locking: Assuming that no concurrency conflicts will occur , only checks for data integrity violations when committing an operation. [1] Optimistic locking cannot solve the problem of dirty reads.
optimistic locking application
Optimistic lock introduction:
Optimistic Locking Compared with pessimistic locking , optimistic locking assumes that data will not cause conflict in general, so when the data is submitted and updated, the data conflict will be formally detected. If a conflict is found , then let the user return the wrong information, let the user decide what to do. So how do we implement optimistic locking? Generally speaking, there are two ways:
1. Implemented using the data version (Version) recording mechanism, which is the most commonly used implementation of optimistic locking. What is a data version? That is, adding a version identifier to the data is generally realized by adding a numeric "version" field to the database table. When reading data, read the value of the version field together, and add one to the version value every time the data is updated. When we submit the update, compare the current version information of the corresponding record in the database table with the version value taken out for the first time. If the current version number of the database table is equal to the version value taken out for the first time, it will be updated. Otherwise, it is considered to be expired data. Use the following picture to illustrate:
As shown in the figure above, if the update operations are performed sequentially, the version of the data will be incremented in sequence, and there will be no conflict. However, if there are different business operations that modify the same version of the data, the operation submitted first (B in the figure) will update the data version to 2. When A submits the update after B, it finds that the version of the data has been modified. , then the update operation of A will fail.
2. The second implementation method of optimistic locking is similar to the first one. It also adds a field to the table that needs optimistic locking control. The name does not matter. The field type uses timestamp , which is similar to the above version. When the update is submitted, check the timestamp of the data in the current database and compare it with the timestamp obtained before the update. If it is consistent, it is OK, otherwise it is a version conflict.
Example of use : Take MySQL InnoDB as an example
Take the previous example as an example: there is a field status in the goods table. If the status is 1, it means that the product has not been ordered. If the status is 2, it means that the product has been ordered. Then we must ensure that the product has been ordered when we place an order for the product. status is 1. Suppose the id of the item is 1.
The ordering operation includes 3 steps:
1. Check out the product information
select (status,status,version) from t_goods where id=#{id}
2. Generate orders based on product information
3. Modify the product status to 2
update t_goods
set status=2,version=version+1
where id=#{id} and version=#{version};
Then in order to use optimistic locking, we first modify the t_goods table, add a version field, and the default version value of the data is 1.
The initial data of the t_goods table is as follows:
For the implementation of optimistic locking, I use MyBatis to practice , as follows:
Goods entity class:
/** * ClassName: Goods <br/> * Function: Commodity entity. <br/>*/ public class Goods implements Serializable { /** * serialVersionUID: Serialization ID. */ private static final long serialVersionUID = 6803791908148880587L; /** * id: primary key id. */ private int id; /** * status: Product status: 1 has not placed an order, 2 has placed an order. */ private int status; /** * name: product name. */ private String name; /** * version: product data version number. */ private int version; @Override public String toString(){ return "good id:"+id+",goods status:"+status+",goods name:"+name+",goods version:"+version; } //setter and getter }
GoodsDao
/** * updateGoodsUseCAS: Use CAS (Compare and set) to update product information * @param goods commodity object * The number of lines affected by @return */ int updateGoodsUseCAS(Goods goods);
mapper.xml
<update id="updateGoodsUseCAS" parameterType="Goods"> <![CDATA[ update t_goods set status=#{status},name=#{name},version=version+1 where id=#{id} and version=#{version} ]]> </update>
GoodsDaoTest test class
@Test public void goodsDaoTest(){ int goodsId = 1; //Query the product information according to the same id and assign it to 2 objects Goods goods1 = this.goodsDao.getGoodsById(goodsId); Goods goods2 = this.goodsDao.getGoodsById(goodsId); //Print the current product information System.out.println(goods1); System.out.println(goods2); //Update product information 1 goods1.setStatus(2);//Modify status to 2 int updateResult1 = this.goodsDao.updateGoodsUseCAS(goods1); System.out.println("Modify product information 1"+(updateResult1==1?"Success":"Failure")); //Update product information 2 goods1.setStatus(2);//Modify status to 2 int updateResult2 = this.goodsDao.updateGoodsUseCAS(goods1); System.out.println("Modify product information 2"+(updateResult2==1?"Success":"Failure")); }
Output result:
good id:1,goods status:1,goods name:道具,goods version:1 good id:1,goods status:1,goods name:道具,goods version:1 Modification of product information 1 succeeded Failed to modify product information 2
illustrate:
In the GoodsDaoTest test method, we simultaneously find out the data of the same version, assign them to different goods objects, and then modify the good1 object first and then perform the update operation, which is successful. Then we modify goods2, and the operation fails when the update operation is performed. At this time, the data in the t_goods table is as follows:
mysql> select * from t_goods; +----+--------+------+---------+ | id | status | name | version | +----+--------+------+---------+ | 1 | 2 | Props | 2 | | 2 | 2 | Equipment | 2 | +----+--------+------+---------+ 2 rows in set mysql>
We can see that the data version with id 1 has been modified to 2 during the first update. So when we update good2, the update where condition does not match, so the update will not succeed. The specific sql is as follows:
update t_goods set status=2,version=version+1 where id=#{id} and version=#{version};
In this way we achieve optimistic locking
Pessimistic Lock Application
Need to use the database lock mechanism, such as SQL SERVER's TABLOCKX (exclusive table lock) When this option is selected, SQL Server will set an exclusive lock on the entire table until the end of the command or transaction. This will prevent other processes from reading or modifying the data in the table.
Used in SqlServer
Begin Tran
select top 1 @TrainNo=T_NO
from Train_ticket with (UPDLOCK) where S_Flag=0
update Train_ticket
set T_Name=user,
T_Time=getdate(),
S_Flag=1
where T_NO=@TrainNo
commit
We use the with (UPDLOCK) option when querying. When querying records, we add an update lock to the record, indicating that we are about to update this record. Note that update locks and shared locks do not conflict, that is Other users can also query the contents of this table, but it conflicts with update locks and exclusive locks. So other update users will block.
in conclusion
In the actual production environment, if the amount of concurrency is not large and dirty reading is not allowed, pessimistic locking can be used to solve the concurrency problem; but if the concurrency of the system is very large, pessimistic locking will bring very big performance problems, so we have to choose A method for optimistic locking.