86

我有以下计数器表:

CREATE TABLE cache (
    key text PRIMARY KEY,
    generation int
);

我想增加一个计数器,或者如果相应的行尚不存在,则将其设置为零。有没有办法在标准 SQL 中没有并发问题的情况下做到这一点?该操作有时是事务的一部分,有时是单独的。

如果可能,SQL 必须在 SQLite、PostgreSQL 和 MySQL 上未经修改地运行。

搜索产生了几个想法,这些想法要么存在并发问题,要么特定于数据库:

  • 尝试INSERT一个新的行,UPDATE如果有错误。不幸的是,错误INSERT中止当前事务。

  • UPDATE行,如果没有行被修改,则为INSERT新行。

  • MySQL 有一个ON DUPLICATE KEY UPDATE子句。

编辑:感谢所有伟大的答复。看起来 Paul 是对的,而且没有一种单一的、可移植的方式来做到这一点。这让我很惊讶,因为这听起来像是一个非常基本的操作。

4

10 回答 10

143

MySQL(以及随后的 SQLite)也支持 REPLACE INTO 语法:

REPLACE INTO my_table (pk_id, col1) VALUES (5, '123');

这会自动识别主键并找到要更新的匹配行,如果没有找到则插入新行。

文档:https ://dev.mysql.com/doc/refman/8.0/en/replace.html

于 2009-03-27T17:15:03.427 回答
33

SQLite 支持替换已经存在的行:

INSERT OR REPLACE INTO [...blah...]

您可以将其缩短为

REPLACE INTO [...blah...]

添加此快捷方式是为了与 MySQLREPLACE INTO表达式兼容。

于 2009-03-27T17:19:10.577 回答
28

我会做类似以下的事情:

INSERT INTO cache VALUES (key, generation)
ON DUPLICATE KEY UPDATE (key = key, generation = generation + 1);

在代码或 sql 中将生成值设置为 0,但使用 ON DUP... 来增加值。无论如何,我认为这就是语法。

于 2009-03-27T17:45:40.997 回答
9

ON DUPLICATE KEY UPDATE 子句是最好的解决方案,因为:REPLACE 执行 DELETE,后跟 INSERT,因此在很短的时间内删除了记录,从而产生很小的可能性,即如果页面被跳过,查询可能会返回在 REPLACE 查询期间查看。

出于这个原因,我更喜欢 INSERT ... ON DUPLICATE UPDATE ...。

jmoz 的解决方案是最好的:虽然我更喜欢 SET 语法而不是括号

INSERT INTO cache 
SET key = 'key', generation = 'generation'
ON DUPLICATE KEY 
UPDATE key = 'key', generation = (generation + 1)
;
于 2009-03-28T22:47:50.280 回答
8

我不知道你会找到一个平台中立的解决方案。

这通常称为“UPSERT”。

查看一些相关讨论:

于 2009-03-27T17:10:43.127 回答
5

在 PostgreSQL 中没有合并命令,实际上编写它并不是微不足道的——实际上有一些奇怪的边缘情况使任务“有趣”。

最好的(如:在最可能的条件下工作)方法是使用函数 - 例如手册(merge_db)中显示的函数。

如果你不想使用函数,你通常可以逃脱:

updated = db.execute(UPDATE ... RETURNING 1)
if (!updated)
  db.execute(INSERT...)

请记住,它不是防错的,它最终失败。

于 2009-03-27T17:41:43.317 回答
4

标准 SQL 为此任务提供了 MERGE 语句。并非所有 DBMS 都支持 MERGE 语句。

于 2009-03-27T17:27:11.650 回答
0

如果您没有一种通用的方式来自动更新或插入(例如,通过事务),那么您可以回退到另一个锁定方案。一个 0 字节的文件、系统互斥体、命名管道等……

于 2009-03-27T17:11:07.680 回答
0

你可以使用插入触发器吗?如果失败,请进行更新。

于 2009-03-27T17:12:25.557 回答
0

如果您可以使用为您编写 SQL 的库,那么您可以使用Upsert(目前仅限 Ruby 和 Python):

Pet.upsert({:name => 'Jerry'}, :breed => 'beagle')
Pet.upsert({:name => 'Jerry'}, :color => 'brown')

这适用于 MySQL、Postgres 和 SQLite3。

它在 MySQL 和 Postgres 中编写存储过程或用户定义函数 (UDF)。它INSERT OR REPLACE在 SQLite3 中使用。

于 2013-11-05T15:06:17.627 回答