当前位置:网站首页>Optimistic and pessimistic affairs
Optimistic and pessimistic affairs
2022-06-27 06:35:00 【Tianxiang shop】
Simple speak , Optimistic business A model is a direct submission , Roll back in case of conflict , Pessimism The model is before the transaction is actually committed , First try to lock the resources that need to be modified , Only after making sure that the transaction can be executed successfully , Just start submitting .
For the optimistic transaction model , It is suitable for the scene with low conflict rate , Because the probability of direct submission will be successful , Conflict is a small probability event , But in the event of a transaction conflict , Rollback can be costly .
The good thing about pessimistic transactions is that they have a high conflict rate , The cost of locking in advance is less than that of rollback afterwards , In addition, it can also solve the scenario where multiple concurrent transactions conflict with each other and no one can succeed at a relatively low cost . However, pessimistic transactions are not as efficient as optimistic transactions in scenarios with low conflict rates .
In terms of the complexity of the application side implementation , Pessimistic transactions are more intuitive , Easier to achieve . Optimistic transactions require a complex application side retry mechanism to ensure .
The following is used bookshop The table in the database implements a book purchase example to demonstrate the difference between optimistic transactions and pessimistic transactions, as well as their advantages and disadvantages . The book purchasing process mainly includes :
- Update inventory quantity
- Create order
- payment
All three operations need to be successful or failed , And in case of concurrency, it is necessary to ensure that it is not oversold .
Pessimism
The following code uses pessimistic transactions , Two threads are used to simulate the process of two users buying the same book concurrently , The bookstore is surplus 10 Ben ,Bob bought 6 Ben ,Alice bought 4 Ben . Two people complete the order at almost the same time , Final , The remaining stock of this book is zero .
When multiple threads are used to simulate multi-user simultaneous insertion , You need to use a thread safe connection object , Use here Java The current popular connection pool HikariCP .
1. Write a pessimistic transaction example
The configuration file
If you use Maven Manage as a package , stay pom.xml Medium <dependencies> In nodes , Add the following dependencies to introduce HikariCP, Set packaging goals at the same time , And JAR Main class of package startup , complete pom.xml As shown below :
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.pingcap</groupId> <artifactId>plain-java-txn</artifactId> <version>0.0.1</version> <name>plain-java-jdbc</name> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>5.0.1</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.3.0</version> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest> <mainClass>com.pingcap.txn.TxnExample</mainClass> </manifest> </archive> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
Code
Then write the code :
package com.pingcap.txn; import com.zaxxer.hikari.HikariDataSource; import java.math.BigDecimal; import java.sql.*; import java.util.Arrays; import java.util.concurrent.*; public class TxnExample { public static void main(String[] args) throws SQLException, InterruptedException { System.out.println(Arrays.toString(args)); int aliceQuantity = 0; int bobQuantity = 0; for (String arg: args) { if (arg.startsWith("ALICE_NUM")) { aliceQuantity = Integer.parseInt(arg.replace("ALICE_NUM=", "")); } if (arg.startsWith("BOB_NUM")) { bobQuantity = Integer.parseInt(arg.replace("BOB_NUM=", "")); } } HikariDataSource ds = new HikariDataSource(); ds.setJdbcUrl("jdbc:mysql://localhost:4000/bookshop?useServerPrepStmts=true&cachePrepStmts=true"); ds.setUsername("root"); ds.setPassword(""); // prepare data Connection connection = ds.getConnection(); createBook(connection, 1L, "Designing Data-Intensive Application", "Science & Technology", Timestamp.valueOf("2018-09-01 00:00:00"), new BigDecimal(100), 10); createUser(connection, 1L, "Bob", new BigDecimal(10000)); createUser(connection, 2L, "Alice", new BigDecimal(10000)); CountDownLatch countDownLatch = new CountDownLatch(2); ExecutorService threadPool = Executors.newFixedThreadPool(2); final int finalBobQuantity = bobQuantity; threadPool.execute(() -> { buy(ds, 1, 1000L, 1L, 1L, finalBobQuantity); countDownLatch.countDown(); }); final int finalAliceQuantity = aliceQuantity; threadPool.execute(() -> { buy(ds, 2, 1001L, 1L, 2L, finalAliceQuantity); countDownLatch.countDown(); }); countDownLatch.await(5, TimeUnit.SECONDS); } public static void createUser(Connection connection, Long id, String nickname, BigDecimal balance) throws SQLException { PreparedStatement insert = connection.prepareStatement( "INSERT INTO `users` (`id`, `nickname`, `balance`) VALUES (?, ?, ?)"); insert.setLong(1, id); insert.setString(2, nickname); insert.setBigDecimal(3, balance); insert.executeUpdate(); } public static void createBook(Connection connection, Long id, String title, String type, Timestamp publishedAt, BigDecimal price, Integer stock) throws SQLException { PreparedStatement insert = connection.prepareStatement( "INSERT INTO `books` (`id`, `title`, `type`, `published_at`, `price`, `stock`) values (?, ?, ?, ?, ?, ?)"); insert.setLong(1, id); insert.setString(2, title); insert.setString(3, type); insert.setTimestamp(4, publishedAt); insert.setBigDecimal(5, price); insert.setInt(6, stock); insert.executeUpdate(); } public static void buy (HikariDataSource ds, Integer threadID, Long orderID, Long bookID, Long userID, Integer quantity) { String txnComment = "/* txn " + threadID + " */ "; try (Connection connection = ds.getConnection()) { try { connection.setAutoCommit(false); connection.createStatement().executeUpdate(txnComment + "begin pessimistic"); // waiting for other thread ran the 'begin pessimistic' statement TimeUnit.SECONDS.sleep(1); BigDecimal price = null; // read price of book PreparedStatement selectBook = connection.prepareStatement(txnComment + "select price from books where id = ? for update"); selectBook.setLong(1, bookID); ResultSet res = selectBook.executeQuery(); if (!res.next()) { throw new RuntimeException("book not exist"); } else { price = res.getBigDecimal("price"); } // update book String updateBookSQL = "update `books` set stock = stock - ? where id = ? and stock - ? >= 0"; PreparedStatement updateBook = connection.prepareStatement(txnComment + updateBookSQL); updateBook.setInt(1, quantity); updateBook.setLong(2, bookID); updateBook.setInt(3, quantity); int affectedRows = updateBook.executeUpdate(); if (affectedRows == 0) { // stock not enough, rollback connection.createStatement().executeUpdate(txnComment + "rollback"); return; } // insert order String insertOrderSQL = "insert into `orders` (`id`, `book_id`, `user_id`, `quality`) values (?, ?, ?, ?)"; PreparedStatement insertOrder = connection.prepareStatement(txnComment + insertOrderSQL); insertOrder.setLong(1, orderID); insertOrder.setLong(2, bookID); insertOrder.setLong(3, userID); insertOrder.setInt(4, quantity); insertOrder.executeUpdate(); // update user String updateUserSQL = "update `users` set `balance` = `balance` - ? where id = ?"; PreparedStatement updateUser = connection.prepareStatement(txnComment + updateUserSQL); updateUser.setBigDecimal(1, price.multiply(new BigDecimal(quantity))); updateUser.setLong(2, userID); updateUser.executeUpdate(); connection.createStatement().executeUpdate(txnComment + "commit"); } catch (Exception e) { connection.createStatement().executeUpdate(txnComment + "rollback"); e.printStackTrace(); } } catch (SQLException e) { e.printStackTrace(); } } }
2. Running examples that do not involve oversold
Run the sample program :
mvn clean package java -jar target/plain-java-txn-0.0.1-jar-with-dependencies.jar ALICE_NUM=4 BOB_NUM=6
SQL journal :
/* txn 1 */ BEGIN PESSIMISTIC /* txn 2 */ BEGIN PESSIMISTIC /* txn 2 */ SELECT * FROM `books` WHERE `id` = 1 FOR UPDATE /* txn 2 */ UPDATE `books` SET `stock` = `stock` - 4 WHERE `id` = 1 AND `stock` - 4 >= 0 /* txn 2 */ INSERT INTO `orders` (`id`, `book_id`, `user_id`, `quality`) VALUES (1001, 1, 1, 4) /* txn 2 */ UPDATE `users` SET `balance` = `balance` - 400.0 WHERE `id` = 2 /* txn 2 */ COMMIT /* txn 1 */ SELECT * FROM `books` WHERE `id` = 1 FOR UPDATE /* txn 1 */ UPDATE `books` SET `stock` = `stock` - 6 WHERE `id` = 1 AND `stock` - 6 >= 0 /* txn 1 */ INSERT INTO `orders` (`id`, `book_id`, `user_id`, `quality`) VALUES (1000, 1, 1, 6) /* txn 1 */ UPDATE `users` SET `balance` = `balance` - 600.0 WHERE `id` = 1 /* txn 1 */ COMMIT
Last , Check the order creation 、 User balance deduction 、 Book inventory deduction , All in line with expectations .
mysql> SELECT * FROM `books`; +----+--------------------------------------+----------------------+---------------------+-------+--------+ | id | title | type | published_at | stock | price | +----+--------------------------------------+----------------------+---------------------+-------+--------+ | 1 | Designing Data-Intensive Application | Science & Technology | 2018-09-01 00:00:00 | 0 | 100.00 | +----+--------------------------------------+----------------------+---------------------+-------+--------+ 1 row in set (0.00 sec) mysql> SELECT * FROM orders; +------+---------+---------+---------+---------------------+ | id | book_id | user_id | quality | ordered_at | +------+---------+---------+---------+---------------------+ | 1000 | 1 | 1 | 6 | 2022-04-19 10:58:12 | | 1001 | 1 | 1 | 4 | 2022-04-19 10:58:11 | +------+---------+---------+---------+---------------------+ 2 rows in set (0.01 sec) mysql> SELECT * FROM users; +----+---------+----------+ | id | balance | nickname | +----+---------+----------+ | 1 | 9400.00 | Bob | | 2 | 9600.00 | Alice | +----+---------+----------+ 2 rows in set (0.00 sec)
3. Run an example to prevent oversold
You can make it more difficult , If the inventory of books is surplus 10 Ben ,Bob Buy 7 Ben , Alice Buy 4 Ben , They placed orders almost at the same time , What will happen ? Continue reusing the code from the previous example to address this requirement , Just put Bob Purchase quantity from 6 Change to 7:
Run the sample program :
mvn clean package java -jar target/plain-java-txn-0.0.1-jar-with-dependencies.jar ALICE_NUM=4 BOB_NUM=7
/* txn 1 */ BEGIN PESSIMISTIC /* txn 2 */ BEGIN PESSIMISTIC /* txn 2 */ SELECT * FROM `books` WHERE `id` = 1 FOR UPDATE /* txn 2 */ UPDATE `books` SET `stock` = `stock` - 4 WHERE `id` = 1 AND `stock` - 4 >= 0 /* txn 2 */ INSERT INTO `orders` (`id`, `book_id`, `user_id`, `quality`) values (1001, 1, 1, 4) /* txn 2 */ UPDATE `users` SET `balance` = `balance` - 400.0 WHERE `id` = 2 /* txn 2 */ COMMIT /* txn 1 */ SELECT * FROM `books` WHERE `id` = 1 FOR UPDATE /* txn 1 */ UPDATE `books` SET `stock` = `stock` - 7 WHERE `id` = 1 AND `stock` - 7 >= 0 /* txn 1 */ ROLLBACK
because txn 2 Get lock resources first , Updated stock,txn 1 Inside affected_rows The return value is 0, Into the rollback technological process .
Check the order creation again 、 User balance deduction 、 Book inventory deduction .Alice Place an order 4 This book is a success ,Bob Place an order 7 This book failed , Inventory surplus 6 This is in line with expectations .
mysql> SELECT * FROM books; +----+--------------------------------------+----------------------+---------------------+-------+--------+ | id | title | type | published_at | stock | price | +----+--------------------------------------+----------------------+---------------------+-------+--------+ | 1 | Designing Data-Intensive Application | Science & Technology | 2018-09-01 00:00:00 | 6 | 100.00 | +----+--------------------------------------+----------------------+---------------------+-------+--------+ 1 row in set (0.00 sec) mysql> SELECT * FROM orders; +------+---------+---------+---------+---------------------+ | id | book_id | user_id | quality | ordered_at | +------+---------+---------+---------+---------------------+ | 1001 | 1 | 1 | 4 | 2022-04-19 11:03:03 | +------+---------+---------+---------+---------------------+ 1 row in set (0.00 sec) mysql> SELECT * FROM users; +----+----------+----------+ | id | balance | nickname | +----+----------+----------+ | 1 | 10000.00 | Bob | | 2 | 9600.00 | Alice | +----+----------+----------+ 2 rows in set (0.01 sec)
Optimistic business
The following code is in an optimistic transaction , Two threads are used to simulate the process of two users buying the same book concurrently , As with the example of pessimistic transactions . The bookstore is surplus 10 Ben ,Bob bought 6 Ben ,Alice bought 4 Ben . Two people complete the order at almost the same time , Final , The remaining stock of this book is zero .
1. Write an optimistic transaction example
Code writing
package com.pingcap.txn.optimistic; import com.zaxxer.hikari.HikariDataSource; import java.math.BigDecimal; import java.sql.*; import java.util.Arrays; import java.util.concurrent.*; public class TxnExample { public static void main(String[] args) throws SQLException, InterruptedException { System.out.println(Arrays.toString(args)); int aliceQuantity = 0; int bobQuantity = 0; for (String arg: args) { if (arg.startsWith("ALICE_NUM")) { aliceQuantity = Integer.parseInt(arg.replace("ALICE_NUM=", "")); } if (arg.startsWith("BOB_NUM")) { bobQuantity = Integer.parseInt(arg.replace("BOB_NUM=", "")); } } HikariDataSource ds = new HikariDataSource(); ds.setJdbcUrl("jdbc:mysql://localhost:4000/bookshop?useServerPrepStmts=true&cachePrepStmts=true"); ds.setUsername("root"); ds.setPassword(""); // prepare data Connection connection = ds.getConnection(); createBook(connection, 1L, "Designing Data-Intensive Application", "Science & Technology", Timestamp.valueOf("2018-09-01 00:00:00"), new BigDecimal(100), 10); createUser(connection, 1L, "Bob", new BigDecimal(10000)); createUser(connection, 2L, "Alice", new BigDecimal(10000)); CountDownLatch countDownLatch = new CountDownLatch(2); ExecutorService threadPool = Executors.newFixedThreadPool(2); final int finalBobQuantity = bobQuantity; threadPool.execute(() -> { buy(ds, 1, 1000L, 1L, 1L, finalBobQuantity, 5); countDownLatch.countDown(); }); final int finalAliceQuantity = aliceQuantity; threadPool.execute(() -> { buy(ds, 2, 1001L, 1L, 2L, finalAliceQuantity, 5); countDownLatch.countDown(); }); countDownLatch.await(5, TimeUnit.SECONDS); } public static void createUser(Connection connection, Long id, String nickname, BigDecimal balance) throws SQLException { PreparedStatement insert = connection.prepareStatement( "INSERT INTO `users` (`id`, `nickname`, `balance`) VALUES (?, ?, ?)"); insert.setLong(1, id); insert.setString(2, nickname); insert.setBigDecimal(3, balance); insert.executeUpdate(); } public static void createBook(Connection connection, Long id, String title, String type, Timestamp publishedAt, BigDecimal price, Integer stock) throws SQLException { PreparedStatement insert = connection.prepareStatement( "INSERT INTO `books` (`id`, `title`, `type`, `published_at`, `price`, `stock`) values (?, ?, ?, ?, ?, ?)"); insert.setLong(1, id); insert.setString(2, title); insert.setString(3, type); insert.setTimestamp(4, publishedAt); insert.setBigDecimal(5, price); insert.setInt(6, stock); insert.executeUpdate(); } public static void buy (HikariDataSource ds, Integer threadID, Long orderID, Long bookID, Long userID, Integer quantity, Integer retryTimes) { String txnComment = "/* txn " + threadID + " */ "; try (Connection connection = ds.getConnection()) { try { connection.setAutoCommit(false); connection.createStatement().executeUpdate(txnComment + "begin optimistic"); // waiting for other thread ran the 'begin optimistic' statement TimeUnit.SECONDS.sleep(1); BigDecimal price = null; // read price of book PreparedStatement selectBook = connection.prepareStatement(txnComment + "SELECT * FROM books where id = ? for update"); selectBook.setLong(1, bookID); ResultSet res = selectBook.executeQuery(); if (!res.next()) { throw new RuntimeException("book not exist"); } else { price = res.getBigDecimal("price"); int stock = res.getInt("stock"); if (stock < quantity) { throw new RuntimeException("book not enough"); } } // update book String updateBookSQL = "update `books` set stock = stock - ? where id = ? and stock - ? >= 0"; PreparedStatement updateBook = connection.prepareStatement(txnComment + updateBookSQL); updateBook.setInt(1, quantity); updateBook.setLong(2, bookID); updateBook.setInt(3, quantity); updateBook.executeUpdate(); // insert order String insertOrderSQL = "insert into `orders` (`id`, `book_id`, `user_id`, `quality`) values (?, ?, ?, ?)"; PreparedStatement insertOrder = connection.prepareStatement(txnComment + insertOrderSQL); insertOrder.setLong(1, orderID); insertOrder.setLong(2, bookID); insertOrder.setLong(3, userID); insertOrder.setInt(4, quantity); insertOrder.executeUpdate(); // update user String updateUserSQL = "update `users` set `balance` = `balance` - ? where id = ?"; PreparedStatement updateUser = connection.prepareStatement(txnComment + updateUserSQL); updateUser.setBigDecimal(1, price.multiply(new BigDecimal(quantity))); updateUser.setLong(2, userID); updateUser.executeUpdate(); connection.createStatement().executeUpdate(txnComment + "commit"); } catch (Exception e) { connection.createStatement().executeUpdate(txnComment + "rollback"); System.out.println("error occurred: " + e.getMessage()); if (e instanceof SQLException sqlException) { switch (sqlException.getErrorCode()) { // You can get all error codes at https://docs.pingcap.com/tidb/stable/error-codes case 9007: // Transactions in TiKV encounter write conflicts. case 8028: // table schema changes case 8002: // "SELECT FOR UPDATE" commit conflict case 8022: // The transaction commit fails and has been rolled back if (retryTimes != 0) { System.out.println("rest " + retryTimes + " times. retry for " + e.getMessage()); buy(ds, threadID, orderID, bookID, userID, quantity, retryTimes - 1); } } } } } catch (SQLException e) { e.printStackTrace(); } } }
Configuration changes
here , Need to pom.xml Start class in :
<mainClass>com.pingcap.txn.TxnExample</mainClass>
Change to :
<mainClass>com.pingcap.txn.optimistic.TxnExample</mainClass>
To point to an example of optimistic business .
2. Running examples that do not involve oversold
Run the sample program :
mvn clean package java -jar target/plain-java-txn-0.0.1-jar-with-dependencies.jar ALICE_NUM=4 BOB_NUM=6
SQL Statement execution procedure :
/* txn 2 */ BEGIN OPTIMISTIC /* txn 1 */ BEGIN OPTIMISTIC /* txn 2 */ SELECT * FROM `books` WHERE `id` = 1 FOR UPDATE /* txn 2 */ UPDATE `books` SET `stock` = `stock` - 4 WHERE `id` = 1 AND `stock` - 4 >= 0 /* txn 2 */ INSERT INTO `orders` (`id`, `book_id`, `user_id`, `quality`) VALUES (1001, 1, 1, 4) /* txn 2 */ UPDATE `users` SET `balance` = `balance` - 400.0 WHERE `id` = 2 /* txn 2 */ COMMIT /* txn 1 */ SELECT * FROM `books` WHERE `id` = 1 for UPDATE /* txn 1 */ UPDATE `books` SET `stock` = `stock` - 6 WHERE `id` = 1 AND `stock` - 6 >= 0 /* txn 1 */ INSERT INTO `orders` (`id`, `book_id`, `user_id`, `quality`) VALUES (1000, 1, 1, 6) /* txn 1 */ UPDATE `users` SET `balance` = `balance` - 600.0 WHERE `id` = 1 retry 1 times for 9007 Write conflict, txnStartTS=432618733006225412, conflictStartTS=432618733006225411, conflictCommitTS=432618733006225414, key={tableID=126, handle=1} primary={tableID=114, indexID=1, indexValues={1, 1000, }} [try again later] /* txn 1 */ BEGIN OPTIMISTIC /* txn 1 */ SELECT * FROM `books` WHERE `id` = 1 FOR UPDATE /* txn 1 */ UPDATE `books` SET `stock` = `stock` - 6 WHERE `id` = 1 AND `stock` - 6 >= 0 /* txn 1 */ INSERT INTO `orders` (`id`, `book_id`, `user_id`, `quality`) VALUES (1000, 1, 1, 6) /* txn 1 */ UPDATE `users` SET `balance` = `balance` - 600.0 WHERE `id` = 1 /* txn 1 */ COMMIT
In optimistic transaction mode , Because the intermediate state is not necessarily correct , It cannot be like a pessimistic transaction pattern , adopt affected_rows To determine whether a statement is successfully executed . You need to see the transaction as a whole , Through the final COMMIT Statement to determine whether the current transaction has a write conflict .
From above SQL The log shows , Since two transactions are executed concurrently , And the same record has been modified ,txn 1 COMMIT Then I threw 9007 Write conflict abnormal . Write conflict about optimistic things , You can retry safely on the application side , Submit successfully after retry , The final implementation results meet the expectations :
mysql> SELECT * FROM books; +----+--------------------------------------+----------------------+---------------------+-------+--------+ | id | title | type | published_at | stock | price | +----+--------------------------------------+----------------------+---------------------+-------+--------+ | 1 | Designing Data-Intensive Application | Science & Technology | 2018-09-01 00:00:00 | 0 | 100.00 | +----+--------------------------------------+----------------------+---------------------+-------+--------+ 1 row in set (0.01 sec) mysql> SELECT * FROM orders; +------+---------+---------+---------+---------------------+ | id | book_id | user_id | quality | ordered_at | +------+---------+---------+---------+---------------------+ | 1000 | 1 | 1 | 6 | 2022-04-19 03:18:19 | | 1001 | 1 | 1 | 4 | 2022-04-19 03:18:17 | +------+---------+---------+---------+---------------------+ 2 rows in set (0.01 sec) mysql> SELECT * FROM users; +----+---------+----------+ | id | balance | nickname | +----+---------+----------+ | 1 | 9400.00 | Bob | | 2 | 9600.00 | Alice | +----+---------+----------+ 2 rows in set (0.00 sec)
3. Run an example to prevent oversold
Let's look at the example of using optimistic transactions to prevent oversold , If the inventory of books is surplus 10 Ben ,Bob Buy 7 Ben , Alice Buy 4 Ben , They placed orders almost at the same time , What will happen ? Continue to reuse the code in the optimistic transaction example to address this requirement , Just put Bob Purchase quantity from 6 Change to 7:
Run the sample program :
mvn clean package java -jar target/plain-java-txn-0.0.1-jar-with-dependencies.jar ALICE_NUM=4 BOB_NUM=7
/* txn 1 */ BEGIN OPTIMISTIC /* txn 2 */ BEGIN OPTIMISTIC /* txn 2 */ SELECT * FROM `books` WHERE `id` = 1 FOR UPDATE /* txn 2 */ UPDATE `books` SET `stock` = `stock` - 4 WHERE `id` = 1 AND `stock` - 4 >= 0 /* txn 2 */ INSERT INTO `orders` (`id`, `book_id`, `user_id`, `quality`) VALUES (1001, 1, 1, 4) /* txn 2 */ UPDATE `users` SET `balance` = `balance` - 400.0 WHERE `id` = 2 /* txn 2 */ COMMIT /* txn 1 */ SELECT * FROM `books` WHERE `id` = 1 FOR UPDATE /* txn 1 */ UPDATE `books` SET `stock` = `stock` - 7 WHERE `id` = 1 AND `stock` - 7 >= 0 /* txn 1 */ INSERT INTO `orders` (`id`, `book_id`, `user_id`, `quality`) VALUES (1000, 1, 1, 7) /* txn 1 */ UPDATE `users` SET `balance` = `balance` - 700.0 WHERE `id` = 1 retry 1 times for 9007 Write conflict, txnStartTS=432619094333980675, conflictStartTS=432619094333980676, conflictCommitTS=432619094333980678, key={tableID=126, handle=1} primary={tableID=114, indexID=1, indexValues={1, 1000, }} [try again later] /* txn 1 */ BEGIN OPTIMISTIC /* txn 1 */ SELECT * FROM `books` WHERE `id` = 1 FOR UPDATE Fail -> out of stock /* txn 1 */ ROLLBACK
From the above SQL The log shows , The first execution is due to write conflict ,txn 1 Retry on the application side , From the latest snapshot obtained, it is found that , The remaining inventory is insufficient , The application side throws out of stock End of exception .
mysql> SELECT * FROM books; +----+--------------------------------------+----------------------+---------------------+-------+--------+ | id | title | type | published_at | stock | price | +----+--------------------------------------+----------------------+---------------------+-------+--------+ | 1 | Designing Data-Intensive Application | Science & Technology | 2018-09-01 00:00:00 | 6 | 100.00 | +----+--------------------------------------+----------------------+---------------------+-------+--------+ 1 row in set (0.00 sec) mysql> SELECT * FROM orders; +------+---------+---------+---------+---------------------+ | id | book_id | user_id | quality | ordered_at | +------+---------+---------+---------+---------------------+ | 1001 | 1 | 1 | 4 | 2022-04-19 03:41:16 | +------+---------+---------+---------+---------------------+ 1 row in set (0.00 sec) mysql> SELECT * FROM users; +----+----------+----------+ | id | balance | nickname | +----+----------+----------+ | 1 | 10000.00 | Bob | | 2 | 9600.00 | Alice | +----+----------+----------+ 2 rows in set (0.00 sec)
边栏推荐
- IDEA一键生成Log日志
- 427-二叉树(617.合并二叉树、700.二叉搜索树中的搜索、98. 验证二叉搜索树、530.二叉搜索树的最小绝对差)
- Assembly language - Wang Shuang Chapter 11 flag register - Notes
- 论文阅读技巧
- 线程间等待与唤醒机制、单例模式、阻塞队列、定时器
- Free SSH and telnet client putty
- Altium Designer 19 器件丝印标号位置批量统一摆放
- 路由器和交换机的区别
- Redis 缓存穿透、缓存击穿、缓存雪崩
- [QT notes] basic use of qregularexpression in QT
猜你喜欢

多线程带来的的风险——线程安全

Distribution gaussienne, régression linéaire, régression logistique

Convolution neural network -- Application of CNN model (ore prospecting prediction)

MPC control of aircraft wingtip acceleration and control surface

研究生数学建模竞赛-无人机在抢险救灾中的优化应用

快速实现单片机和手机蓝牙通信

The fourth question of the 299th weekly match 6103 Minimum fraction of edges removed from the tree

JVM常用指令

Dev++ 环境设置C语言关键字显示颜色

Create a basic WDM driver and use MFC to call the driver
随机推荐
TiDB与 MySQL 兼容性对比
Restrictions on the use of tidb
Keep 2 decimal places after multiplying SQLSEVER fields
1317. convert an integer to the sum of two zero free integers
426 binary tree (513. find the value in the lower left corner of the tree, 112. sum of paths, 106. construct a binary tree from the middle order and post order traversal sequence, 654. maximum binary
技术人员创业一年心得
My opinion on test team construction
427- binary tree (617. merge binary tree, 700. search in binary search tree, 98. verify binary search tree, 530. minimum absolute difference of binary search tree)
多线程带来的的风险——线程安全
Thinking technology: how to solve the dilemma in work and life?
Program ape learning Tiktok short video production
Ahb2apb bridge design (2) -- Introduction to synchronous bridge design
JS to implement bidirectional data binding
路由器和交换机的区别
426-二叉树(513.找树左下角的值、112. 路径总和、106.从中序与后序遍历序列构造二叉树、654. 最大二叉树)
Inter thread wait and wake-up mechanism, singleton mode, blocking queue, timer
汇编语言-王爽 第8章 数据处理的两个基本问题-笔记
Once spark reported an error: failed to allocate a page (67108864 bytes), try again
ORA-00909: 参数个数无效,concat引起
Matlab quickly converts two-dimensional coordinates of images into longitude and latitude coordinates