这不是一个好的解决方案,因为它在执行 SELECT 时会在 my_tbl 上创建一个共享锁。任意数量的线程可以同时拥有一个共享锁,但它会阻塞并发写锁。所以这会导致插入序列化,等待 SELECT 完成。
你可以观察这个锁。在一个会话中启动此查询:
INSERT INTO my_tbl (idx, clmn_1)
SELECT IFNULL(MAX(idx), 0) + 1, 1234+SLEEP(60)
FROM my_tbl;
然后转到另一个会话并运行 innotop 并查看锁定屏幕(按“L”键)。你会看到这样的输出:
___________________________________ InnoDB Locks ___________________________________
ID Type Waiting Wait Active Mode DB Table Index Ins Intent Special
61 TABLE 0 00:00 00:00 IS test my_tbl 0
61 RECORD 0 00:00 00:00 S test my_tbl PRIMARY 0
这就是为什么自动增量机制以它的方式工作的原因。无论事务隔离如何,插入线程都会短暂锁定表,只是为了增加 auto-inc 编号。这是非常快的。然后释放锁,允许其他线程立即继续。同时,第一个线程尝试完成其插入。
有关自动增量锁定的更多详细信息,请参阅http://dev.mysql.com/doc/refman/5.5/en/innodb-auto-increment-handling.html。
我不确定为什么要模拟自动增量行为,而不是将列定义为自动增量列。 您可以将现有表更改为自动递增。
回复您的评论:
即使 PK 被声明为自动增量,您仍然可以指定一个值。仅当您未在 INSERT 中指定 PK 列,或者您指定NULL
orDEFAULT
作为其值时,自动增量才会启动。
CREATE TABLE foo (id INT AUTO_INCREMENT PRIMARY KEY, c CHAR(1));
INSERT INTO foo (id, c) VALUES (123, 'x'); -- inserts value 123
INSERT INTO foo (id, c) VALUES (DEFAULT, 'y'); -- inserts value 124
INSERT INTO foo (id, c) VALUES (42, 'n'); -- inserts specified value 42
INSERT INTO foo (c) VALUES ('Z'); -- inserts value 125
REPLACE INTO foo (id, c) VALUES (125, 'z'); -- changes existing row with id=125
回复您的评论:
START TRANSACTION;
SELECT IFNULL(MAX(idx), 0)+1 FROM my_tbl FOR UPDATE;
INSERT INTO my_tbl (idx, clmn_1) VALUES (new_idx_val, some_val);
COMMIT;
这实际上比您的第一个想法更糟糕,因为现在SELECT...FOR UPDATE
创建的是 X 锁而不是 S 锁。
您真的不应该尝试重新发明 AUTO-INCREMENT 的行为,因为任何 SQL 解决方案都受 ACID 属性的限制。Auto-inc 必须在 ACID 之外工作。
如果您需要以原子方式更正现有行,请使用REPLACE 或 INSERT...ON DUPLICATE KEY UPDATE。