I have a simple table
CREATE TABLE test (
col INT,
data TEXT,
KEY (col)
);
and a simple transaction
START TRANSACTION;
SELECT * FROM test WHERE col = 4 FOR UPDATE;
-- If no results, generate data and insert
INSERT INTO test SET col = 4, data = 'data';
COMMIT;
I am trying to ensure that two copies of this transaction running concurrently result in no duplicate rows and no deadlocks. I also don't want to incur the cost of generating data
for col = 4
more than once.
I have tried:
SELECT ..
(withoutFOR UPDATE
orLOCK IN SHARE MODE
):Both transactions see that there are no rows with
col = 4
(without acquiring a lock) and both generatedata
and insert two copies of the row withcol = 4
.SELECT .. LOCK IN SHARE MODE
Both transactions acquire a shared lock on
col = 4
, generatedata
and attempt to insert a row withcol = 4
. Both transactions wait for the other to release their shared lock so it canINSERT
, resulting inERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
.SELECT .. FOR UPDATE
I would expect that one transaction's
SELECT
will succeed and acquire an exclusive lock oncol = 4
and the other transaction'sSELECT
will block waiting for the first.Instead, both
SELECT .. FOR UPDATE
queries succeed and the transactions proceed to deadlock just like withSELECT .. LOCK IN SHARE MODE
. The exclusive lock oncol = 4
just doesn't seem to work.
How can I write this transaction without causing duplicate rows and without deadlock?
UNIQUE
index and useINSERT ... ON DUPLICATE KEY
orINSERT IGNORE
instead. – SentINSERT .. ON DUPLICATE KEY UPDATE
then both transactions will incur the cost of generatingdata
, unnecessarily. If I doINSERT IGNORE ..
then all errors MySQL encounters while running theINSERT
will be ignore (not just duplicate keys), which is pretty sloppy. – Ladonnaladytx_isolation
? – ElephantREPEATABLE READ
(SERIALIZABLE
would just make theSELECT ..
version behave the same asSELECT .. LOCK IN SHARE MODE
) – Ladonnalady