sql-server - 原子 DROP 和 SELECT ... INTO 表
问题描述
我会认为像下面这样的代码是原子的:如果 DeleteMe 在运行这个事务之前存在,它应该被删除并重新创建。否则应该简单地创建它:
BEGIN TRANSACTION
IF OBJECT_ID('DeleteMe') IS NOT NULL
DROP TABLE DeleteMe
SELECT query.*
INTO DeleteMe
FROM (SELECT 1 AS Value) AS query
COMMIT TRANSACTION
但是,似乎同时执行此代码多次可能会导致各种错误组合:
- 无法删除表“DeleteMe”,因为它不存在或您没有权限。
- 数据库中已经有一个名为“DeleteMe”的对象。
这是一个 LINQPad 脚本来说明我的意思。
var sql = @"
BEGIN TRANSACTION
IF OBJECT_ID('DeleteMe') IS NOT NULL
DROP TABLE DeleteMe
SELECT query.*
INTO DeleteMe
FROM (SELECT 1 AS Value) AS query
COMMIT TRANSACTION
";
await Task.WhenAll(Enumerable.Range(1, 50)
.Select(async i =>
{
using var connection = new SqlConnection(this.Connection.ConnectionString);
await connection.OpenAsync();
await connection.ExecuteAsync(sql);
}).Dump());
以及它的输出示例:
如果我使用 SQL Server 2016 的DROP TABLE IF EXISTS
功能,该部分至少看起来是原子的,但是另一个并发命令显然仍然可以在删除该表和再次创建它之间创建 DeleteMe 表。
问题:有什么方法可以原子地删除、创建和填充表,这样从另一个并发连接的角度来看,没有时间该表不存在?
解决方案
有没有办法原子地删除、创建和填充表,这样从另一个并发连接的角度来看,没有时间该表不存在?
当然。这就像任何事务一样:您必须在第一条语句上使用不一致的锁。在您的事务中,两个会话可以同时运行IF OBJECT_ID('DeleteMe') IS NOT NULL
。然后他们都试图放下物体,只有一个成功。
DROP TABLE IF EXISTS
还执行存在性检查,然后再对删除它所必需的对象进行排他模式锁定。
获得排他锁的一种简单而可靠的方法是使用sp_getapplock。
例如
BEGIN TRANSACTION
exec sp_getapplock 'dropandcreate_DeleteMe', 'exclusive'
DROP TABLE IF EXISTS DeleteMe
SELECT query.*
INTO DeleteMe
FROM (SELECT 1 AS Value) AS query
COMMIT TRANSACTION
推荐阅读
- reactjs - 测试要使用 jest 调用的组件的 onClose 事件
- php - 尝试运行任务时,发送图像,收到错误代码 = 13“查询已取消”
- json - 使用转义变量在 jq 中提取值
- createprocess - Eiffel:如何创建和使用 UTIL 类,或“从类中调用静态方法”
- perl - 如何在 perl 中使用 Date::Calc 获取上个月的日期?
- python - 绘制人口和组的分布
- google-cloud-functions - 如何访问 Google Cloud Function 执行指标?
- unity3d - Unity SteamVR - 强制脱离手
- sql-server - 当我查看表设计和脚本表与 information_schema 和 sys.columns 时,为什么我会得到不同的列比例?
- javascript - 无法将 SocketIO 服务器从 docker 容器连接到外部