Overview
Assume that there is a table which controls stock amount information.
CREATE TABLE products(
id INTEGER PRIMARY KEY,
remaining_amount INTEGER NOT NULL
);
INSERT INTO products(id, remaining_amount) VALUES (1, 1);
Now, user A and B try to take the last stock at the same time.
A/B: UPDATE products
SET remaining_amount = remaining_amount - 1
WHERE id = 1 and remaining_amount > 0;
The questions are:
- Could
remaining_amountnever be negative values? Do we need any explicit pessimistic row locking? - Which transaction level should I use:
READ COMMITTED,REPEATABLE READ,SERIALIZABLEorREAD UNCOMMITTED(only for MySQL)? - Does it yield different conclusion with different RDBMS?
Related Information
- (mysql innodb) Is single update statement with "where" transaction safe?
- This question concludes that explicit pessimistic row locking is required for MySQL.
- My Twitter friend RDBMS geek says that:
- Oracle tries to achieve write consistency; if the target row has been changed, the consecutive UPDATE query is automatically rolled back and it retries with implicit pessimistic row locking. He says that it is described in this book: Amazon | Expert Oracle Database Architecture | Kyte, Thomas, Kuhn, Darl | Software Development
- PostgreSQL has immutable rows and then the old rows are treated as dead tuple; so the latter updates will never be applied.
We noticed that each RDBMS(MySQL/Postgres/Oracle) ALWAYS, AUTOMATICALLY locks updating rows WITH ANY TRANSACTION ISOLATION LEVELS. It means that MySQL(InnoDB)'s
READ UNCOMMITTEDworks well.SET TRANSACTION ISOLATIONLEVEL READ UNCOMMITTED;BEGIN;SET TRANSACTION ISOLATIONLEVEL READ UNCOMMITTED;BEGIN;UPDATE products SET remaining_amount = remaining_amount - 1 WHERE remaining_amount > 0;Rows matched: 1 Changed: 1 Warnings: 0
UPDATE products SET remaining_amount = remaining_amount - 1 WHERE remaining_amount > 0;COMMIT;Rows matched: 0 Changed: 0 Warnings: 0
COMMIT;Special Thanks: @zyake
Other evidences on Postgres: https://twitter.com/zyake/status/1543229753968041984
UPDATE: Detailed investigation on MySQL/Postgres
mpyw-yattemita/mysql-postgres-update-locking-and-isolation-levels
However, it is not guaranteed to be conflict-free whenever a
SELECTsubquery is included. Our investigation revealed that the results vary depending on the transaction isolation level, as follows:UPDATE t SET v=v-1 WHERE id=1 AND v>0UPDATE t SET v=v-1 WHERE EXISTS(SUBQUERY)UPDATE t SET v=(SUBQUERY)-1 WHERE id=1 AND v>0Postgres
SET, useREAD COMMITED.WHERE, useREPEATABLE READand retry on serialization errors.Subquery WHERE with
READ COMMITEDwill be broken:MySQL
SET, any transaction isolation level works well.READ UNCOMMITTEDorREAD COMMITTEDare recommended.WHERE, useREPEATABLE READand retry on deadlock errors.Subquery WHERE with
READ UNCOMMITEDwill be broken:Subquery WHERE with
READ COMMITEDwill be broken:Subquery WHERE with
REPEATABLE READwill get deadlocks:Subquery WHERE with
SERIALIZABLEwill get deadlocks: