如果表中的行不存在,如何更新或插入它?
我有以下计数器表:
CREATE TABLE cache (
key text PRIMARY KEY,
generation int
);
我想增加其中一个计数器,或者如果相应的行尚不存在,则将其设置为零。 有没有办法在标准 SQL 中没有并发问题的情况下做到这一点? 该操作有时是事务的一部分,有时是单独的。
如果可能的话,SQL 必须在 SQLite、PostgreSQL 和 MySQL 上运行且未经修改。
搜索产生了几个想法,这些想法要么存在并发问题,要么特定于数据库:
尝试
INSERT
新行,如果出现错误则UPDATE
。 不幸的是,INSERT
上的错误会中止当前事务。UPDATE
行,如果没有修改任何行,则INSERT
一个新行。MySQL 有一个
ON DUPLICATE KEY UPDATE
子句。
编辑:感谢所有精彩的回复。 看来保罗是对的,而且没有一种单一的、可移植的方法可以做到这一点。 这让我感到非常惊讶,因为这听起来像是一个非常基本的操作。
I have the following table of counters:
CREATE TABLE cache (
key text PRIMARY KEY,
generation int
);
I would like to increment one of the counters, or set it to zero if the corresponding row doesn't exist yet. Is there a way to do this without concurrency issues in standard SQL? The operation is sometimes part of a transaction, sometimes separate.
The SQL must run unmodified on SQLite, PostgreSQL and MySQL, if possible.
A search yielded several ideas which either suffer from concurrency issues, or are specific to a database:
Try to
INSERT
a new row, andUPDATE
if there was an error. Unfortunately, the error onINSERT
aborts the current transaction.UPDATE
the row, and if no rows were modified,INSERT
a new row.MySQL has an
ON DUPLICATE KEY UPDATE
clause.
EDIT: Thanks for all the great replies. It looks like Paul is right, and there's not a single, portable way of doing this. That's quite surprising to me, as it sounds like a very basic operation.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
MySQL(以及随后的 SQLite)还支持 REPLACE INTO 语法:
这会自动识别主键并查找要更新的匹配行,如果未找到则插入新行。
文档: https://dev.mysql.com/doc/refman/8.0 /en/replace.html
MySQL (and subsequently SQLite) also support the REPLACE INTO syntax:
This automatically identifies the primary key and finds a matching row to update, inserting a new one if none is found.
Documentation: https://dev.mysql.com/doc/refman/8.0/en/replace.html
SQLite 支持替换行(如果已存在):
您可以将其缩短
为此快捷方式已添加为与 MySQL
REPLACE INTO
表达式兼容。SQLite supports replacing a row if it already exists:
You can shorten this to
This shortcut was added to be compatible with the MySQL
REPLACE INTO
expression.我会执行如下操作:
在代码或 sql 中将生成值设置为 0,但使用 ON DUP... 来增加该值。 我认为无论如何这就是语法。
I would do something like the following:
Setting the generation value to 0 in code or in the sql but the using the ON DUP... to increment the value. I think that's the syntax anyway.
ON DUPLICATE KEY UPDATE 子句是最佳解决方案,因为:
REPLACE 会先执行 DELETE,然后再执行 INSERT,因此在很短的一段时间内,记录会被删除,从而导致如果在 REPLACE 查询期间查看页面,则查询可能会跳过该记录而返回。
出于这个原因,我更喜欢插入...重复更新...。
jmoz的解决方案是最好的:
虽然我更喜欢 SET 语法而不是括号
the ON DUPLICATE KEY UPDATE clause is the best solution because:
REPLACE does a DELETE followed by an INSERT so for an ever so slight period the record is removed creating the ever so slight possibility that a query could come back having skipped that if the page was viewed during the REPLACE query.
I prefer INSERT ... ON DUPLICATE UPDATE ... for that reason.
jmoz's solution is the best:
though I prefer the SET syntax to the parentheses
我不知道您是否会找到平台中立的解决方案。
这通常称为“UPSERT”。
请参阅一些相关讨论:
I don't know that you are going to find a platform-neutral solution.
This is commonly called an "UPSERT".
See some related discussions:
在 PostgreSQL 中没有合并命令,实际上编写它并不简单 - 实际上有一些奇怪的边缘情况使任务“有趣”。
最好的(如:在最可能的条件下工作)方法是使用函数 - 例如 手册 (merge_db)。
如果您不想使用函数,您通常可以这样做:
只要记住它不是防错的,它最终将会失败。
In PostgreSQL there is no merge command, and actually writing it is not trivial - there are actually strange edge cases that make the task "interesting".
The best (as in: working in the most possible conditions) approach, is to use function - such as one shown in manual (merge_db).
If you don't want to use function, you can usually get away with:
Just remember that it is not fault proof and it will fail eventually.
标准 SQL 提供了用于此任务的 MERGE 语句。 并非所有 DBMS 都支持 MERGE 语句。
Standard SQL provides the MERGE statement for this task. Not all DBMS support the MERGE statement.
如果您没有原子更新或插入的通用方法(例如,通过事务),那么您可以回退到另一个锁定方案。 0 字节文件、系统互斥体、命名管道等...
If you don't have a common way to atomically update or insert (e.g., via a transaction) then you can fallback to another locking scheme. A 0-byte file, system mutex, named pipe, etc...
你能使用插入触发器吗? 如果失败,请进行更新。
Could you use an insert trigger? If it fails, do an update.
如果您同意使用为您编写 SQL 的库,那么您可以使用 Upsert (目前仅限 Ruby 和 Python):
适用于 MySQL、Postgres 和 SQLite3。
它在 MySQL 和 Postgres 中编写存储过程或用户定义函数 (UDF)。 它在 SQLite3 中使用
INSERT OR REPLACE
。If you're OK with using a library that writes the SQL for you, then you can use Upsert (currently Ruby and Python only):
That works across MySQL, Postgres, and SQLite3.
It writes a stored procedure or user-defined function (UDF) in MySQL and Postgres. It uses
INSERT OR REPLACE
in SQLite3.