【问题标题】:Are SQL queries guaranteed to execute atomically when using UNION?使用 UNION 时,SQL 查询是否保证原子执行?
【发布时间】:2011-04-08 16:38:13
【问题描述】:

我正在发出一个由多个使用 UNION 分组的 SELECT 组成的 SQL 查询:

SELECT *
FROM   employee 
       LEFT JOIN department 
          ON employee.DepartmentID = department.DepartmentID
UNION
SELECT *
FROM   employee
       RIGHT JOIN department
          ON employee.DepartmentID = department.DepartmentID;

假设我在READ_COMMITTED事务隔离下执行这个查询,两个SELECT语句是否保证原子执行?或者我是否冒着在单个 SELECT 语句之间更改数据的风险? SQL 规范是否讨论过这类事情?

澄清:当我说“原子”时,我并不是指 ACID 中的“A”。我的意思是,在查询完成之前,我希望部门和员工表都被读锁定。

【问题讨论】:

  • 为什么不直接使用全联接?
  • @dlev:我使用的数据库 (H2) 不支持完全联接。

标签: sql transactions


【解决方案1】:

是的,该语句是原子的,但是数据可以在两次读取之间发生变化。

Read Committed 仅保证您不会读取脏数据,它对读取的一致性没有任何其他承诺,因为您需要更高的隔离级别。

正如你所说,你会接受一个 SQL Server 示例......

连接 1

(假设在悲观读取已提交隔离级别下)

CREATE TABLE employee
(
name VARCHAR(50),
DepartmentID INT
)

CREATE TABLE department
(
DepartmentID INT
)

INSERT INTO department VALUES (1)
INSERT INTO employee VALUES ('bob',1)

declare @employee TABLE
(
name VARCHAR(50),
DepartmentID INT
)


WHILE ((SELECT COUNT(*) FROM @employee) < 2)
BEGIN
DELETE FROM  @employee

INSERT INTO @employee
SELECT employee.*
FROM   employee 
       LEFT JOIN department 
          ON employee.DepartmentID = department.DepartmentID
UNION
SELECT employee.*
FROM   employee
       RIGHT JOIN department
          ON employee.DepartmentID = department.DepartmentID

END;          

SELECT * FROM @employee

连接 2

while (1=1)
UPDATE employee SET name = CASE WHEN name = 'bob' THEN 'bill' else 'bob' END

现在回到连接 1

name                                               DepartmentID
-------------------------------------------------- ------------
bill                                               1
bob                                                1

(记得切换回连接 2 来杀死它!)

涵盖此READ COMMITED行为is here的具体文档

共享锁类型决定何时 它将被释放。行锁是 在下一行之前释放 处理。页面锁被释放 读取下一页时,表格 语句时释放锁 完成。

【讨论】:

  • @Martin 在这种情况下如何将存储过程视为原子的?
  • @Martin:如果我理解正确,每个 SELECT 都保证原子执行,但整体查询不是。如果不能保证它的子部分彼此一致,那么你怎么能说它是原子的呢?
  • @Gili - 我的意思是他们会在ACID 见到A。 Isolation等级is a different thing
  • @Martin:我澄清了这个问题。当我写“atomic”时,我并不是指 Acid 中的 A。
  • @Gili - 我可以确认在 SQL Server 中,在悲观的read committed 隔离级别下肯定没有这样的保证,你需要不同的隔离级别来实现这一点。可能很快就会删除这个答案,因为我对 H2 或 SQL 标准对此事的说法一无所知!
【解决方案2】:

使用UNION 将删除可能从任一联合查询返回的任何重复记录,因此不完全是原子的。如果您想要来自所有联合查询的所有记录,请使用 UNION ALL。 UNION ALL 也可以比 UNION 快​​得多。

【讨论】:

  • UNION ALL 明显更快,但在这种情况下,在其中一个连接中,您需要一个过滤掉 INNER JOIN 记录的条件,否则它们将被重复。
  • 这个问题主要与正确性有关,而不是性能。我正在尝试进行 FULL OUTER JOIN,但我的数据库 (H2) 不支持它。
【解决方案3】:

编辑:请注意我的答案不正确,但我不想删除它,因为我认为它链接到好问题并且有好的 cmets。

每个单独的事务都是原子的。

使用多个子查询的UNION 是单个 T-SQL 命令、单个事务,并且是原子的。

这部分是避免低效查询(或存储过程,就此而言)的原因,因为它们的原子性质会延迟其他事务。

编辑: 有关子查询原子性的更多有趣信息,请参阅此问题

Is update with nested select atomic operation?

编辑:显然我错了。

这是关于该主题的一个很好的讨论:Atomic UPSERT in SQL Server 2005 Remus 是一个很好的例子。抱歉怀疑你,马丁....

【讨论】:

  • @Matthew:当 READ_COMMITTED 允许不可重复读取时,你怎么能说“每个单独的事务都是原子的”?
  • @Gili 读锁适用于整个事务...我不知道您是否可以为子查询明确说明其他情况(尽管这对我来说似乎很危险)
  • @MatthewPK - 对H2 一无所知,但对于处于读取提交隔离级别的 SQL Server,一旦读取页面,共享锁就会被释放。自联接等完全有可能在同一行有 2 个不同版本。
  • @Martin,这可能是真的,但队列中的下一个读取将是事务中的下一个子查询。因此,除非某些嵌套查询导致行在范围内发生更改,否则我不应该期望它会更改。
  • 您能否链接到支持您所说内容的官方文档/规范?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多