在重要的
提交庆典之前的几周
-PostgreSQL 11的
feature freeze
版本之前的最后一个-
黑客通讯,压缩左侧包装中的芯片组,观看了
MERGE惊悚片。
2ndQuadrant的惊悚导演兼首席执行官
Simon Riggs试图推出一个补丁,以令人印象深刻的毅力和独创性实现MERGE命令的语法。 Riggs自2009年以来一直是喜剧演员,您可以凭借自己的喜剧演员身份来批准补丁。 他同样受到备受尊敬的PostgreSQL委员会和退伍军人的反对。 激情清晰而隐含地泛滥成灾,甚至没有引起直接侮辱,这对于许多国内论坛的常客来说是一个令人惊讶的事实。 但是,当问题解决后,到目前为止仍然存在一些紧张局势,没有什么可争辩的。
但是,激情就是激情(将在后面进行讨论),我想无动于衷地梳理出这个完全牵强的问题的实质。

外部合并
如果要完全简化,那就是这样:我们有2个表,它们具有相同的字段和不同的数据。 假设姓名和年龄。 我们需要将它们合并为一个。 但是有必要决定如何处理两个表中的个性。 我们很可能希望将所有内容都保留在决赛桌中,并将信息更新为匹配的个人。 显然,即使在这种情况下,这也是非常常见的任务。 它可以在没有
MERGE
情况下解决,而发出复杂的请求,也可以使用触发器等。 但这很不方便。 但是,MERGE的非规范版本称为UPSERT(UPdate + inSERT)解决了此问题。
MERGE运算符符合SQL-2003标准,并且在SQL-2008中已具有很高的声誉。 它是在Oracle,DB2和MS SQL中实现的,这意味着缺少MERGE将使那些考虑从这些DBMS迁移到PostgreSQL的人感到沮丧。 西蒙·里格斯(Simon Riggs)尽可能快的渴望已在PostgreSQL 11中,这是由2ndQuadrant客户的愿望所推动的,而不是出于野心或争吵。
实际上,MERGE具有丰富的功能,不必从表中特别是结构相似的表中获取数据。
命令语法如下:
MERGE INTO tablename USING table_reference ON (condition) WHEN MATCHED THEN UPDATE SET column1 = value1 [, column2 = value2 ...] WHEN NOT MATCHED THEN INSERT (column1 [, column2 ...]) VALUES (value1 [, value2 ...]);
但是,您可以这样:
MERGE [hint] INTO [schema .] {table | view} [table_alias] USING { subquery | [schema .] { table | view}} [table_alias] ON ( condition ) [ merge_update_clause ] [ merge_insert_clause ] [ error_logging_clause ] ;
该语法在Oracle中实现。 换句话说,然后MERGE执行操作以单个SQL命令中使用data_source修改目标表target_table_name中的记录,该命令可以根据条件对target_table_name中的记录执行INSERT,UPDATE或DELETE。 在这种情况下,target_table_name可以是一个视图,data_source可以是一组
表或视图,这是子查询的结果 。
首先,
MERGE
使用
target_table_name
在
data_source
上执行
left outer join
target_table_name
,建议使用0个或更多候选更改记录;
WHEN
子句按指定顺序计算; 只要满足条件,就会执行相应的操作。 关键字
WHEN [NOT] MATCH THEN
在
SQL
不是很常见,因此我们提醒您,这是其他语言中的
if-else
类的控件构造。 对于
target_table_name
,
MERGE
行为与
UPDATE, INSERT
或
DELETE
相同,只是整个命令的语法不同。
带有
ON
的子句必须在主键的所有列上建立连接,或者,如果指定了其他列,则必须使用一些唯一索引,以便
[NOT] MATCHED
立即确定候选记录的操作,以排除与其他事务的交互。
MERGE
确定性命令:您不能在同一MERGE命令中多次更新同一记录。
一个例子:
MERGE CustomerAccount CA USING RecentTransactions T ON T.CustomerId = CA.CustomerId WHEN MATCHED THEN UPDATE SET Balance = Balance + TransactionValue WHEN NOT MATCHED THEN INSERT (CustomerId, Balance) VALUES (T.CustomerId, T.TransactionValue);
或使用子查询:
MERGE INTO bonuses D USING (SELECT employee_id, salary, department_id FROM employees WHERE department_id = 80) S ON (D.employee_id = S.employee_id) WHEN MATCHED THEN UPDATE SET D.bonus = D.bonus + S.salary*.01 DELETE WHERE (S.salary > 8000) WHEN NOT MATCHED THEN INSERT (D.employee_id, D.bonus) VALUES (S.employee_id, S.salary*.01) WHERE (S.salary <= 8000);
在
IBM DB2中,语法也将起作用。
正如他们所说 ,“在幕后”将与
UPDATE FROM
构造类似。
自2008年以来,
MS SQL 还拥有 MERGE
。
但是即使在单一的标准语法之后,也开始出现从大量机制和实现方法中进行选择的问题。 团队应在不同级别的事务隔离下工作,并使用不同的锁定算法,重点关注竞争激烈或不太合适的操作模式。 而且,您可能已经猜到,要实现这种复杂的逻辑,您需要接触许多DBMS组件。
UPSERT,伪合并
显然,DBMS开发人员正在寻找折衷解决方案,拒绝从字面上重现标准语法。 这种方法的优点是自由。 您可以使用针对特定DBMS的有机机制,可以优化您认为与用户最相关的任务的实现。
例如,在
MySQL中,有一个
REPLACE
命令可用作
INSERT
,但如果新行和旧行在
PRIMARY KEY
或
UNIQUE
索引中具有相同的值,则在插入新行之前,旧行将被杀死。 但是也有
INSERT ... ON DUPLICATE KEY UPDATE
发生
INSERT
和
UPDATE
(而不是
REPLACE
的
DELETE
)。 这是
UPSERT
。
还有 INSERT IGNORE
,它只是不执行插入,而不会在目标表的某些限制下引发错误(但警告)。
PG合并编年史
在PostgreSQL社区中,关于MERGE的讨论始于2005年,当时Jaime Casanova询问
社区中是否有人已经开始开发 MERGE
。
彼得·埃森特拉特 ( Peter Eisentraut) 建议讨论 PostgreSQL是否应该开发MERGE选项之一:类似于MySQL的实现,还是更好地将工作指向Oracle的MERGE
类型的功能轻量级版本。 但是,朝这个方向努力是值得的吗?在简短的讨论中,
西蒙·里格斯 (
Simon Riggs)的故事主角出现了:
MERGE对于OLTP系统和DW(数据仓库-数据仓库,即分析应用程序)都非常有用,在这些应用程序中,复杂的查询但竞争不太激烈的环境和数据很少更新,如果更新,通常通常是大块。<...>我们可以将MERGE作为COPY FROM的变体来实现,这将非常酷。每个人都同意:是的,很酷。 更准确地说,几乎是所有东西:
Stephen Frost :
我并不是唯一一个说我需要成熟,合规的MERGE标准的人。Bruce Momjian有一个不同的,更务实的建议:
在我看来,我们需要在 MERGE
实现 一些我们可以实现的选项,而在其他情况下,我们将给出一个错误(在需要阻塞整个表的情况下)。 在获得用户反馈后,我们将思考下一步该怎么做。但是到目前为止,什么都没有发生。
冰破了
2008年, 西蒙·里格斯 ( Simon Riggs)再次敦促与MERGE合作-选择哪种方式(到那时,仍在起草中的SQL-2008标准的新版本MERGE已经出现)。 他详细描述了当时Oracle,IBM和MS SQL的当前实现以及MySQL和Teradata的替代语法。 不久之后,他已经提到
在2ndQuadrant朝这个方向
开始工作 。
Peter Eisentraut
在他的博客上写道 :
当然,Riggs是最有资格的专家之一,他可以领导MERGE的实施工作。但是,这里出现了第一个意外的转折:一个
学生卷入了这个问题-参与了
GSoC程序(即Google Summer of Code)开发的参与者。 他的名字叫
Boxuan Bxzhai-我不打算抄写姓氏。 不久,他写道工作已经完成。
但几乎不算在内。 2ndQuadrant(即Simon Riggs盟友)的
Greg Smith写道:
因此,我们的代码中有一个补丁,其中有六个严重的未解决的问题。 我对那些小人物保持沉默。 问题太深,无法最终确定commitfest的代码。 同时,很长一段时间以来,Boxuan都没有听到任何声音。 我们可以帮助他,但是他在哪里? 谁知道?关于实现路径的讨论在
2014年再次爆发,但再次没有发生:没有代码。
最后,早在
2017年, 西蒙·里格斯 (
Simon Riggs)写道:
我正在研究将MERGE
提交到PostgreSQL版本11的代码。 我们使用已经在起作用的INSERT ON CONFLICT
基础相同的机制,因此不需要更改基础结构,基本上只是在可用的基础上实现语法。 但是我从头开始编写代码,不使用以前的开发。我们正在谈论的是那时的Peter Geoghegan(
VMware )已经以9.5替代
INSERT .. ON CONFLICT UPDATE
语法实现了
INSERT .. ON CONFLICT UPDATE
,不同于SQL标准,但仍与MySQL中的
MERGE
和
REPLACE
有关。
起初,西蒙的作品受到尼斯作品的感叹! 但是,
罗伯特·哈斯 (
Robert Haas )虽然支持,但警告可能存在序列化异常。 例如,要处理
INSERT .. ON CONFLICT UPDATE
,如果没有MERGE的基础,它就比较平静。
PostgreSQL
UPSERT
作者
UPSERT
本人:
我不会混合使用ON CONFLICT DO UPDATE
和MERGE
。 <...>例如,对于加载大块数据( bulk load
),我将使用merge join
算法。 <...>通常, MERGE
的优点与正常连接将以通常的方式在那里工作有关: nested loop, hash, merge
。 在INSERT … ON CONFLICT
根本没有任何联接。Haas:
像Peter一样,我认为如果这样做,那么执行DML
请求时的锁定很强。 只有一个人一次可以与MERGE
,这不太可能使任何人高兴。出于好奇:Geigan在
这里和
这里分解了
MERGE
和
MERGE
之间的细微差别和明显差异(我们在我们的网站上存储了PostgreSQL的
归档通信 )。
西蒙抗拒。 他呼吁最近的历史。 就像关于断面一样,他们还说“一种新语法,仅此而已”。 但是事实证明这是非常有用的事情。
但是我不建议立即意识到MERGE中的所有内容。 我们将像分区一样做-我们将开发划分为多个阶段。在我看来,还有一种说法是很有说服力的:
好。 但是让我们选择。 我建议一个实用的选择。 从认真尝试开发MERGE
十年即将到来 。 是不是该开始做某件事,获得一些有用的解决方案,而不是再等10年的完美解决方案了? 假设它存在。最终,补丁到达社区。 几号 想象一下。 不,他们没有想到:西蒙(Simon)在2017年12月30日寄给他。 并规定这是一个WIP补丁,即“进行中的工作”-一个正在工作的补丁。
西蒙,一月:
补丁已完成,没有任何特殊错误。 1200行代码以及测试和文档。 我将把他委托给这个commitfest,稍后我们将完成RLS(行级安全性-记录级的保护)和分区支持。委员会等级
在这里,我们必须退一步,解释专员在社区中的作用。 历史上,专员的功能(即有权接受下一版本补丁的专员)的功能已发生变化。 很久以前,在开发人员很少的情况下,慷慨地分配了提交权。 例如,著名的(在完全不同的领域中)
朱利安·阿桑奇 (
Julian Assange)获得了司令官头衔,只有六个补丁的作者。 现在要成为专员并不容易,几十个人中就没有新贵。 Boyus Momdjan(
EnterpriseDB )有13,363次提交,Tom Lane(Tom Lane,
Crunchy Data )13127,Robert Haas(
EnterpriseDB )-2074。顺便说一下,
来自俄罗斯的
唯一提交人是
Fedor Sigaev (Postodors
Professional的 Teodor Sigaev)进行了383次提交。 。 西蒙·里格斯(Simon Riggs)本人有449个人,我再说一遍:作为专员,他有足够的权力来接管和提交补丁-他和他的雇员。 另一件事是,坦率地忽略其他主要知名委员会的意见,这样做是不值得的。 他们还可以剥夺专员的身份,但是至少他们会
revert
补丁。
战斗中的骨折
当然,通常在草率的“无希望”补丁中,他们发现了新的错误。 新版本随之而来。
1月底,出现了一个新角色:2ndQuadrant
Pavan (他的名字叫所有人;完全是Pavan Deolasee)的开发者。 现在,社区正在处理一连串的问题:Pavan发送了新版本并感谢批评,而Simon则以巨大的营销压力打破了它们。
哈斯(Haas):
我不认为就排除适用于所有地方的功能做出单方面的决定是不值得的。 如果我们同意此修补程序中不包含某些功能-这是一回事。 与此不同的是,所有人在这次评论中都表示了不同意见。 而且我们实际上没有听到为什么应该排除这些功能的原因。逻辑表示如下:
- 先验地,存在着严重的问题,因为它们不能不以“骑兵进攻”的方式发展。
- 即使在当前版本中接受补丁之后,也可以完成对重要功能的支持,例如版本10-11中的新分区,CTE(公用表表达式= WITH查询)或RLS(行级安全性),但前提是所提议的体系结构适合在顶部构建她想要的功能。
第二位Peter Geigan提出了这一点:
通常,我会注意各种功能的支持,因为如果有的话,它会增强人们普遍认为设计应按要求进行的认识 。 并且,如果这样的问题是由于WITH
表达式的支持(即CTE
)引起的,那么我的想法是,底层体系结构会在这里和那里引起问题。同时,第X个小时(最后一个委员会节日)临近,MERGE上的乌云正在聚集。 创始之父并不是专门在Simon和Pavan制作的补丁的体系结构中发现严重问题。 我不必寻找问题;他们愿意开放自己。
结局即将来临
情节正在加速。 尽管其他委员会对此工作态度冷淡,但
在4月2日, Simon决定在
SQL:2016补丁后提交
Command ,添加了文件,Depesz(Hubert Lubachevsky)设法在他的博客上
宣布了该文件,但在同一天,Simon将所有内容回滚了,因为错误。
第二天,通过添加
WITH
支持再次提交。
作为回应,这些指控确实是严肃的。
Andres Freund (
EnterpriseDB )写道:
解析器和执行程序中MERGE的体系结构并没有给我留下深刻的印象。 在解析分析期间创建隐藏的联接是一个非常糟糕的主意。 执行程序的这种结构必须完全更改。汤姆·莱恩:
解析树的设计很弱。您重载了InsertStmt
函数,他继续说,它根本不会执行INSERT
,但是它随机地具有与原始字段相同的字段。 不是全部,而是一些。 这是不好的,它导致混乱。让我们添加对
Fedor Sigayev的观察:
在解析器中, MERGE
与MERGE
相关的INSERT
节点, MERGE
节点上悬挂着许多其他字段。 如果您在ANALIZE
查看执行计划,则不会立即了解您是在处理常规INSERT
还是在处理MERGE
:要了解,您需要查看其他字段。西蒙,冷静地说:
好,我们将更改它,明天再发送一个新文件 。
哈斯:
我同意彼得的看法。 架构的选择是不成功的。西蒙不放弃。
4月6日 ,响应对Tom Lane的批评,提交了一个新的修补程序,该修补程序已在解析器中进行了修改。
谈判与投降
布鲁斯·莫姆扬(Bruce Momjan)
4月6日 :
我想指出的是,人们并没有要求您努力解决紧急问题。 他们要求您撤回补丁。 当然,您可以努力工作,希望他们会改变主意,但是-再次-他们没有问您。西蒙:
如果汤姆[Lane]和安德烈斯[Freund]在接下来的几天中仍然感到他们的恐惧没有消除,我将很乐意退一步 。汤姆·莱恩(Tom Lane):
我仍然投票赞成将补丁回滚。 即使他现在很完美,现在人们也没有时间相信这一点-涉及其他紧急问题。仅此而已。
西蒙说好了,在
MERGE
的战斗结束了。 所有补丁都被抽回,主题已移动到下一个commitfest,状态为“等待作者完成”。 表演的参加者取得了和平。

然而,从最近几周的往来来看,似乎仍然存在一些紧张局势。
应许的道德
- 幸运的是,PostgreSQL社区拥有自然的和正式的机制,可以(几乎)无冲突地筛选未成熟解决方案的尝试。 即使他们受到公司负责人的尊敬,也为PostgreSQL的发展做出了巨大贡献。 缺乏功能的客户正在努力进行投资。
- 不幸的是,社区经常停滞不前。 采用甚至明确相关的发展都是惯性的。 有时包括非理性的完美主义。 我工作的Postgres Professional的经验证实了这一点。 我们打了3年的重要重要补丁INCLUDE 。 用于JSON / JSONB的一系列有用补丁仍在等待中。 “将自己的发展带给社区”一词的意思并不是真正的付出,而是一拳打定 :向来宾张开双臂迎接客人,并陪同隔离。
PS:
作者的免责声明 :我们只是想展示一些社区生活。 所有名称匹配都是随机的:)
PPS:武士
Natalia Levshina 。