【问题标题】:Does SQL Server wrap Select...Insert Queries into an implicit transaction?SQL Server 是否将 Select...Insert Queries 包装到隐式事务中?
【发布时间】:2010-11-07 11:06:06
【问题描述】:

当我执行一个选择/插入查询时,SQL Server 是否会自动创建一个隐式事务并因此将其视为一个原子操作?

如果表中不存在值,请执行以下查询:

INSERT INTO Table1 (FieldA)
SELECT 'newvalue' 
WHERE NOT EXISTS (Select * FROM Table1 where FieldA='newvalue')

如果我没有明确地将“newvalue”包含在事务中,那么在评估 WHERE 子句和执行 INSERT 子句之间,是否有可能被另一个用户插入到表中?

【问题讨论】:

  • FieldA 是否(或为什么不使)成为具有唯一属性的关键字段?似乎是避免重复的好方法,这似乎是您正在寻找的。​​span>
  • 无法详细说明,但由于复杂的原因,我无法直接向数据库添加约束(因此我对这种类型的查询进行了解决)。
  • 好吧.. 只是这样我看起来不会太脑残。我将提供一点背景。数据库由第 3 方产品控制/拥有,我正在通过他们的 API 查询该产品,该 API 构建了执行的实际 SQL。它不支持事务和修改其数据库的对象可能会使保修失效。 =)
  • 选择/插入操作可能是原子操作的一个明显指标是断点包括整个语句。
  • Autocommit mode 是 SQL Server 数据库引擎的默认事务管理模式。每个 Transact-SQL 语句在完成时都会提交或回滚。如果语句成功完成,则提交;如果遇到任何错误,则回滚。只要此默认模式未被显式或隐式事务覆盖,与数据库引擎实例的连接就会在自动提交模式下运行。自动提交模式也是 ADO、OLE DB、ODBC 和 DB-Library 的默认模式。

标签: sql-server tsql transactions


【解决方案1】:

您混淆了事务和锁定。如果出现任何错误,事务会将您的数据恢复到原始状态。如果没有,它会将数据移动到新状态。在处理操作时,您永远不会让您的数据处于间歇状态。另一方面,锁定允许或阻止多个用户同时访问数据。要回答您的问题,select...insert 是原子的,只要没有明确请求粒度锁,在 select..insert 正在进行时其他用户将无法插入。

【讨论】:

  • 没那么多。我明白其中的区别。我提到事务的原因主要是因为这似乎是 SQL Server 中用于指定关键部分/锁的关键机制。无论如何,感谢您的回答。
  • “在处理操作时,您永远不会让数据处于间歇状态。” - 如果 READ UNCOMMITTED 隔离级别允许脏读,如果随后由执行脏读的任何进程写入,则回滚的修改记录可以使其回到数据库中,从技术上讲,这将是一个间歇状态。
  • 事务和锁就像 cookie 和牛奶 - "TransactionsREAD UNCOMMITTED 级别运行不会发出 共享锁防止其他事务修改当前事务读取的数据。READ UNCOMMITTED 事务也不会被排他锁阻止,排他锁会阻止当前事务读取已修改但未由其他事务提交的行。"
【解决方案2】:

约翰,这个问题的答案取决于您当前的隔离级别。如果您设置为 READ UNCOMMITTED,您可能会遇到麻烦,但使用更高的隔离级别,您不应该在 select 和 insert 之间的表中获得额外的记录。使用 READ COMMITTED(默认)、REPEATABLE READ 或 SERIALIZABLE 隔离级别,您应该会被覆盖。

【讨论】:

  • 我假设在这种情况下使用默认值 (READ COMMITTED),因为它使用隐式事务而不是显式事务。听起来对吗?
  • 是的 - 隔离级别与事务是分开的,所以除非您使用锁定提示或通过显式设置来更改它,否则它将是相同的。
【解决方案3】:

使用 SSMS 2016,可以验证 Select/Insert 语句请求锁定(因此很可能以原子方式操作):

  1. 为以下事务打开一个新的查询/连接,并在启动调试器之前在ROLLBACK TRANSACTION 上设置断点:

    BEGIN TRANSACTION     
    INSERT INTO Table1 (FieldA) VALUES ('newvalue');    
    ROLLBACK TRANSACTION --[break-point]
    
  2. 在上述断点处,从单独的查询窗口执行以下命令以显示任何锁(可能需要几秒钟来注册任何输出):

    SELECT * FROM sys.dm_tran_locks
     WHERE resource_database_id = DB_ID()
       AND resource_associated_entity_id = OBJECT_ID(N'dbo.Table1');
    

    应该有一个与上述BEGIN TRANSACTION/INSERT 关联的锁(因为默认情况下在READ COMMITTEDISOLATION LEVEL 中运行)

    OBJECT      **  **********  *   IX  LOCK    GRANT   1   
    
  3. 从另一个 SSMS 实例中,打开一个新查询并运行以下命令(同时仍停在上述断点处):

    INSERT INTO Table1 (FieldA)
    SELECT 'newvalue'
    WHERE NOT EXISTS (Select * FROM Table1 where FieldA='newvalue') 
    

    这应该与字符串“(Executing)...”一起显示在查询窗口的选项卡标题中(因为默认情况下@@LOCK_TIMEOUT 为 -1)。

  4. 第 2 步重新运行查询。

    现在应该显示与Select/Insert 对应的另一个锁:

    OBJECT      **  **********  0   IX  LOCK    GRANT   1
    OBJECT      **  **********  0   IX  LOCK    GRANT   1
    

参考: How to check which locks are held on a table

【讨论】:

猜你喜欢
  • 2021-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多