我有一张像idx (PK) clmn_1
两者都是INT。 idx
不是
定义为自动增量,但我试图模拟它。至
插入此表,我正在使用:
"INSERT INTO my_tbl (idx, clmn_1) \
SELECT IFNULL(MAX(idx), 0) + 1, %s \
FROM my_tbl", val_clmn_1
现在,这有效。我的查询是关于原子性的。由于我读取然后插入到同一个表中,当多个插入同时发生时,可能会有一个 重复键错误?
而且,我该如何自己测试?
我正在使用Percona XtraDB服务器5.5。
答案 0 :(得分:2)
这不是一个好的解决方案,因为它在执行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。
我不确定为什么要模拟自动增量行为,而不是仅将列定义为自动增量列。 You can change an existing table to be auto-incrementing.
重新评论:
即使PK被声明为自动增量,您仍然可以指定一个值。如果不在INSERT中指定PK列,或者指定NULL
或DEFAULT
作为其值,则自动增量仅会启动。
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之外工作。
如果您需要以原子方式更正现有行,请使用either REPLACE or INSERT...ON DUPLICATE KEY UPDATE。