MySQL base notes (17) - optimistic and pessimistic locking lock

1. What is the optimistic locking, pessimistic locking

  1. Optimistic locking means that when data is read, the default is not believed that this thread to modify data (in Java Atomic atomic class is so designed, such as CAS), but only when submitted to the judge to determine whether or not in line with expectations the need to spin and read again.Suitable for reading and writing fewer occasions.
  2. Pessimistic locking means that when data is read think there must be other threads modify data, directly added mutex, until their complete processing of the data after the release of the lock (Java is pessimistic in synchronized lock),Suitable for write once read many fewer occasions

The two .MySQL optimistic locking and pessimistic locking

  1. DDL
CREATE TABLE goods (
  id int(20) NOT NULL AUTO_INCREMENT,
  name varchar(100) DEFAULT NULL,
  stock int(20) DEFAULT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY idx_name (name) USING BTREE
) ENGINE=InnoDB 
  1. DML
INSERT INTO goods VALUES (1, "MacBookPro2015", 1000);
INSERT INTO goods VALUES (2, "MacBookPro2016", 1000);
INSERT INTO goods VALUES (3, "MacBookPro2017", 1000);
INSERT INTO goods VALUES (4, "MacBookPro2018", 1000);
INSERT INTO goods VALUES (5, "MacBookPro2019", 1000);

1. pessimistic locking

1. mutex -for update

First thing to say is mutex, synchronize keywords similar to Java, to use

select ... for update

Which, after the inquiry into the row is locked mutex line only when the locking thread has submitted the transaction, will be lifted mutex, other threads before this can not pass for updateor lock in share modego to acquire the lock, data security is assured under strict concurrency for a great performance security requirements of the situation, but will greatly reduce concurrency. So, mutex again become a write lock.

  1. Thread 1
mysql> begin; // 0.开启事务
mysql> select * from goods where id = 1 for update; // 1.使用互斥锁
+----+----------------+-------+
| id | name           | stock |
+----+----------------+-------+
|  1 | MacBookPro2015 |  1000 |
+----+----------------+-------+
1 row in set (0.00 sec)

mysql> update goods set stock = stock - 1 where id = 1; // 2.减少库存
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit; // 4.提交事务
Query OK, 0 rows affected (0.01 sec)
  1. Thread 2
mysql> select * from goods where id = 1; // 3.不获取互斥锁进行查询,查询出来的结果是错的
+----+----------------+-------+
| id | name           | stock |
+----+----------------+-------+
|  1 | MacBookPro2015 |  1000 |
+----+----------------+-------+
1 row in set (0.00 sec)

mysql> select * from goods where id = 1 for update; // 3.使用互斥锁查询,获取不到互斥锁,阻塞
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> select * from goods where id = 1 lock in share mode;// 3.使用共享锁查询,获取不到共享锁,阻塞
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> select * from goods where id = 1 for update; // 5.在线程1提交事务释放互斥锁后可使用互斥锁查询
+----+----------------+-------+
| id | name           | stock |
+----+----------------+-------+
|  1 | MacBookPro2015 |   999 |
+----+----------------+-------+

Other cases, such as update, delete, etc. are the same, after using a mutex, only wait for its release before they can get to and execute

2. Shared lock -lock in share mode

Use:

select...lock in share mode

Also belongs to a shared lock pessimistic locking, but it is different mutex lock that:Before locking the row has not been modified, other threads can also be used to obtain the shared lock and read the data, but can not be modified; after locking the row is modified, another thread can not acquire the shared lock to read the data, share upgraded to lock mutex. So, a shared lock is also known as a read lock.

  1. Thread 1
mysql> begin;    // 0.开启事务
Query OK, 0 rows affected (0.00 sec)

mysql> select * from goods where id = 1 lock in share mode; // 1.获取共享锁
+----+----------------+-------+
| id | name           | stock |
+----+----------------+-------+
|  1 | MacBookPro2015 |   999 |
+----+----------------+-------+
1 row in set (0.00 sec)

mysql> update goods set stock = stock - 1 where id = 1;  // 3.更新库存
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.00 sec) // 5.提交事务
  1. Thread 2
mysql> select * from goods where id = 1; // 2. 普通查询,不加锁,可以查询
+----+----------------+-------+
| id | name           | stock |
+----+----------------+-------+
|  1 | MacBookPro2015 |   999 |
+----+----------------+-------+
1 row in set (0.00 sec)

mysql> select * from goods where id = 1 for update; // 2.无法获取互斥锁
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> select * from goods where id = 1 lock in share mode; // 2. 可以获取共享锁进行查询
+----+----------------+-------+
| id | name           | stock |
+----+----------------+-------+
|  1 | MacBookPro2015 |   999 |
+----+----------------+-------+
1 row in set (0.00 sec)

mysql> update goods set stock = stock - 1 where id = 2; // 2.无法获取共享锁进行修改
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted

mysql> select * from goods where id = 1 lock in share mode; // 4.无法获取共享锁,无法查询,因为线程1已经修改了库存,此时升级为互斥锁
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> select * from goods where id = 1 lock in share mode; // 6.线程1释放共享锁,线程2获取共享锁
+----+----------------+-------+
| id | name           | stock |
+----+----------------+-------+
|  1 | MacBookPro2015 |   998 |
+----+----------------+-------+
1 row in set (2.30 sec)
3.update, insert, delete automatic row lock

The above two cases are cases in SQL statements manually locked, in Innodb engine, update, insert, deletethese three statements is the default mutex, if you are using update...for updateor update...lock in share modewill error:

mysql> update goods set stock = stock - 1 for update;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'for update' at line 1
mysql> update goods set stock = stock - 1 lock in share mode;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'lock in share mode' at line 1

Simple test:

  1. Thread 1
mysql> begin;
Query OK, 0 rows affected (0.01 sec)

mysql> update goods set stock = stock - 1 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
  1. Thread 2
mysql> select * from goods where id = 1 for update;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> select * from goods where id = 1 lock in share mode;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted

2. Optimistic locking - version control

To apply optimistic locking in MySQL, typically by adding a row version number field to achieve, when we operate, first check out the version number of the data, when we finished modifying the data, and then determine the version update again version number to avoid inconsistencies. To successfully modify the data version number increment. If the version number is different from previously described is modified, the above operation is repeated to, (In InnoDB also use this mechanism to solve the problem of phantom read RR circumstances, be called MVCC Multi Version Concurrency Control). Golang to use the following simple implementation of optimistic locking logic.

mysql> alter table goods add version int(20)  not null default 0;//添加版本号字段
type Goods struct {
	Id int `gorm:"column:id"`
	Name string `gorm:"column:name"`
	Stock int `gorm:"column:stock"`
	Version int `gorm:"column:version"`
}

func main() {
	db := getDb()
	var good Goods
	db.Raw("select * from goods where id = 1").Scan(&good)
	fmt.Println(good)
	for good.Id != 0 && good.Stock > 0 {
		// 获取到版本号
		version := good.Version
		// 减库存并增加版本号,以当前版本号为条件之一
		exec := db.Exec("update goods set stock = stock - 1, version = ? where id = 1 and version = ?", version+1, version)
		if exec.RowsAffected != 0 {
			fmt.Println("更新成功")
			break
		}
		fmt.Println("更新失败,重试")
	}
	db.Raw("select * from goods where id = 1").Scan(&good)
	fmt.Println(good)
}

result:

{1 MacBookPro2015 997 3}
更新成功
{1 MacBookPro2015 996 4}
Published 309 original articles · won praise 205 · Views 300,000 +

Guess you like

Origin blog.csdn.net/pbrlovejava/article/details/103862808