执行事务时为更新/删除/插入锁定表

Posted

技术标签:

【中文标题】执行事务时为更新/删除/插入锁定表【英文标题】:Lock table for Update/Delete/Insert when executing a transaction 【发布时间】:2020-03-07 18:53:46 【问题描述】:

我正在尝试使用 How to delete large data of table in SQL without log? 将我的 DELETE 语句转换为 TRUNCATE

这是我正在尝试的,

   -- Move recent records from Main table to a Temp table
   -- TRUNCATE the Main table
   -- Return back data from Temp table to Main table

在此期间,我想停止任何 INSERT/UPDATE/DELETE 语句(直到运行 TRUNCATE 语句)在我的主表上运行,因为如果我允许,那么我们可能会在 TRUNCATE 期间丢失一些数据。

【问题讨论】:

TRUNCATE 将在操作期间获取排他表锁,并阻止并发活动。 @DanGuzman 你的意思是我不想担心这个吗? TRUNCATE 将删除表中的所有数据。您能否详细说明您担心哪些数据丢失?如果表中有您不想丢失的数据,请不要截断或删除。 @DanGuzman 想想这个。在将数据移动到 Temp 表之后,在 TRUNCATE 开始之前,如果有任何数据 INSERTED/UPDATED/DELETED 将丢失。 我明白你现在的意思了。如果您使用 INSERT...SELECT... WITH(TABLOCKX) 移动数据,则 TRUNCATE 并在同一个事务中全部移回。那么就没有损失的风险。 【参考方案1】:

TRUNCATE 语句获取SCH-M 锁意味着它创建了一个模式修改锁

第二种锁是模式修改锁——SCH-M。这把锁 类型由正在更改元数据和实时的会话获取 交易期间。这个锁可以描述为 超级独占锁,它与任何其他锁类型不兼容 包括意图锁

Locking in Microsoft SQL Server (Part 13 – Schema locks)

在此期间,更新、选择和删除语句将等待表截断操作。因此,CRUD 操作将自动停止,直到 TRUNCATE 语句完成。

【讨论】:

看我上面的评论,after moving data to the Temp table and before TRUNCATE starts if there is any data INSERTED/UPDATED/DELETED will be lost【参考方案2】:

以下是使用SWITCHTRUNCATE 减少记录完整恢复模型的示例脚本。 SWITCH 是一个快速的元数据操作。 TRUNCATE 执行的空间释放是由具有更大表 (64MB+) 的异步后台线程完成的,因此与 DELETE 相比,它也很快并且大大减少了日志记录;

事务用于确保全有或全无行为,并在事务期间持有架构修改锁以在此过程中停止数据修改。

以下是示例处理前后使用的事务日志空间,初始为 1M 行,保留 50K:

+--------+---------------+--------------------+
|        | Log Size (MB) | Log Space Used (%) |
+--------+---------------+--------------------+
| Before |      1671.992 |           27.50415 |
| After  |      1671.992 |           30.65533 |
+--------+---------------+--------------------+

测试设置:

--example main table
CREATE TABLE dbo.Main(
      MainID int NOT NULL CONSTRAINT PK_Main PRIMARY KEY
    , MainData char(1000) NOT NULL
);
--staging table with same schema and indexes as main table
CREATE TABLE dbo.MainStaging(
      MainID int NOT NULL CONSTRAINT PK_MainStaging PRIMARY KEY
    , MainData char(1000) NOT NULL
);
--load 1M rows into main table for testing
WITH 
     t10 AS (SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t(n))
    ,t1k AS (SELECT 0 AS n FROM t10 AS a CROSS JOIN t10 AS b CROSS JOIN t10 AS c)
    ,t1g AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS num FROM t1k AS a CROSS JOIN t1k AS b CROSS JOIN t1k AS c)
INSERT INTO dbo.Main WITH(TABLOCKX) (MainID, MainData) 
SELECT num, CAST(num AS char(1000))
FROM t1g
WHERE num <= 1000000;
GO

示例脚本:

SET XACT_ABORT ON; --ensures transaction is rolled back immediately even if script is cancelled
BEGIN TRY

    BEGIN TRAN;

    --truncate in same transaction so entire script can be safely rerun
    TRUNCATE TABLE dbo.MainStaging;

    --ALTER TABLE will block other activity until committed due to schema modification lock
    --main table will be empty after switch
    ALTER TABLE dbo.Main SWITCH TO dbo.MainStaging;

    --keep 5% of rows
    INSERT INTO dbo.Main WITH(TABLOCKX) (MainID, MainData)
    SELECT MainID, MainData
    FROM dbo.MainStaging
    WHERE MainID > 950000;

    COMMIT;

END TRY
BEGIN CATCH

    IF @@TRANCOUNT > 0 ROLLBACK;
    THROW;

END CATCH;
GO

【讨论】:

【参考方案3】:

尝试使用事务:

BEGIN TRANSACTION

SELECT TOP 1 *
FROM table_name
WITH (TABLOCK, HOLDLOCK)

-- do your stuff

COMMIT 

【讨论】:

TRUNCATE 默认在自动提交事务中运行。除非同一事务中有其他语句,否则显式事务不提供价值。

以上是关于执行事务时为更新/删除/插入锁定表的主要内容,如果未能解决你的问题,请参考以下文章

删除阻止插入

Django 事务锁定表

自治事务在 Oracle 中有自己的会话吗?

Firestore是否提供更多的锁定事务,比如表到表的概念?

快照隔离事务由于更新冲突而中止,但没有事务开始

SQL Server:其他用户的事务锁定表吗?