2016-09-09 66 views
1

我試圖將1,500,000記錄插入到表格中。在插入過程中面臨表鎖問題。所以我想出了下面的批量插入。在不鎖定表格的情況下插入大量記錄

DECLARE @BatchSize INT = 50000 

WHILE 1 = 1 
    BEGIN 
     INSERT INTO [dbo].[Destination] 
        (proj_details_sid, 
        period_sid, 
        sales, 
        units) 
     SELECT TOP(@BatchSize) s.proj_details_sid, 
          s.period_sid, 
          s.sales, 
          s.units 
     FROM [dbo].[SOURCE] s 
     WHERE NOT EXISTS (SELECT 1 
         FROM dbo.Destination d 
         WHERE d.proj_details_sid = s.proj_details_sid 
           AND d.period_sid = s.period_sid) 

     IF @@ROWCOUNT < @BatchSize 
     BREAK 
    END 

我有Destination(proj_details_sid ,period_sid)聚集索引。 NOT EXISTS部分只是爲了限制插入的記錄再次插入表

我是這樣做的嗎,這會避免表鎖嗎?或者有沒有更好的辦法。

注:採取時間與批次,無批量插入

+0

加上(nolock)裏面存在的子句,以避免等待鎖 –

+0

@AksheyBhat添加NOLOCK將在這裏造成任何問題,由於髒讀 –

+0

@AksheyBhat只是好奇,SQL將數據插入'目標'表,這'nolock '在'exists clause'中不會生效,不是嗎? – Prisoner

回答

2

鎖升級是不太可能與你所說的SELECT部分相關。

這是一個natural consequence of inserting a large number of rows

使用ALTER TABLE SET LOCK_ESCALATION選項時,鎖升級是不是在桌子上禁用

鎖升級就被觸發,當任一下列條件存在:

  • 單個Transact-SQL語句在單個未分區的表或索引上獲取至少5,000個鎖。
  • 單個Transact-SQL語句在分區表的單個分區上獲取至少5,000個鎖,並將ALTER TABLE SET LOCK_ESCALATION選項設置爲AUTO。
  • 數據庫引擎實例中的鎖數超過了內存或配置閾值。

如果由於鎖衝突而無法升級鎖,數據庫引擎會在獲取的每1,250個新鎖上定期觸發鎖升級。

通過跟蹤Profiler中的鎖升級事件或簡單地嘗試使用不同的批處理大小,您可以很容易地看到這一點。對我來說TOP (6228)顯示6250鎖持有,但TOP (6229)隨着鎖升級開始,它突然下降到1。確切的數字可能會有所不同(取決於數據庫設置和當前可用的資源)。使用試驗和錯誤來查找出現鎖定升級的閾值。

CREATE TABLE [dbo].[Destination] 
    (
    proj_details_sid INT, 
    period_sid  INT, 
    sales   INT, 
    units   INT 
) 

BEGIN TRAN --So locks are held for us to count in the next statement 
INSERT INTO [dbo].[Destination] 
SELECT TOP (6229) 1, 
        1, 
        1, 
        1 
FROM master..spt_values v1, 
     master..spt_values v2 

SELECT COUNT(*) 
FROM sys.dm_tran_locks 
WHERE request_session_id = @@SPID; 

COMMIT 

DROP TABLE [dbo].[Destination] 

要插入50,000行這樣幾乎肯定鎖定升級將嘗試。

文章How to resolve blocking problems that are caused by lock escalation in SQL Server已經很老了,但很多建議仍然有效。

  1. 打碎大的批處理操作成幾個較小的操作(即使用更小的批量大小),如果不同的SPID當前持有一個不兼容的表鎖不能發生
  2. 鎖升級 - 他們給出的例子是一個不同的會話執行

BEGIN TRAN 
SELECT * FROM mytable (UPDLOCK, HOLDLOCK) WHERE 1=0 
WAITFOR DELAY '1:00:00' 
COMMIT TRAN 
  • 通過啓用跟蹤標誌1211禁用鎖升級 - 然而,這是一個全球環境,並可能導致嚴重問題。有一個較新的選項1224,問題較少,但這仍然是全球性的。
  • 另一種選擇是ALTER TABLE blah SET (LOCK_ESCALATION = DISABLE),但這仍然不是很有針對性,因爲它會影響所有對錶的查詢,而不僅僅是您在這裏的單一場景。

    所以我會選擇選項1或可能選項2,並打折其他。

    0

    我加入(NOLOCK)或多或少相同的目標表 - > dbo.Destination(NOLOCK)。 現在,你不會鎖定你的桌子。

    WHILE 1 = 1 
        BEGIN 
         INSERT INTO [dbo].[Destination] 
            (proj_details_sid, 
            period_sid, 
            sales, 
            units) 
         SELECT TOP(@BatchSize) s.proj_details_sid, 
              s.period_sid, 
              s.sales, 
              s.units 
         FROM [dbo].[SOURCE] s 
         WHERE NOT EXISTS (SELECT 1 
             FROM dbo.Destination(NOLOCK) d 
             WHERE d.proj_details_sid = s.proj_details_sid 
               AND d.period_sid = s.period_sid) 
    
         IF @@ROWCOUNT < @BatchSize 
         BREAK 
        END 
    
    +0

    添加'NOLOCK'會導致任何問題,由於髒讀? –

    +0

    如果你的程序不能並行運行,髒讀操作不會造成問題。 –

    +0

    在上述情況下,NOLOCK不會造成任何問題。 –

    0

    要做到這一點,你可以在你的select語句中使用WITH(NOLOCK)。 但不建議在OLTP數據庫上使用NOLOCK。在Destination

    1

    不是檢查的數據存在,似乎最好先存放在臨時表中的所有數據,並批量插入Destination

    參考:Using ROWLOCK in an INSERT statement (SQL Server)

    DECLARE @batch int = 100 
    DECLARE @curRecord int = 1 
    DECLARE @maxRecord int 
    
    -- remove (nolock) if you don't want to have dirty read 
    SELECT row_number over (order by s.proj_details_sid, s.period_sid) as rownum, 
         s.proj_details_sid, 
         s.period_sid, 
         s.sales, 
         s.units 
    INTO #Temp 
    FROM [dbo].[SOURCE] s WITH (NOLOCK) 
    WHERE NOT EXISTS (SELECT 1 
            FROM dbo.Destination d WITH (NOLOCK) 
            WHERE d.proj_details_sid = s.proj_details_sid 
              AND d.period_sid = s.period_sid) 
    
    -- change this maxRecord if you want to limit the records to insert 
    SELECT @maxRecord = count(1) from #Temp 
    
    WHILE @maxRecord >= @curRecord 
        BEGIN 
         INSERT INTO [dbo].[Destination] 
           (proj_details_sid, 
           period_sid, 
           sales, 
           units) 
         SELECT proj_details_sid, period_sid, sales, units 
         FROM #Temp 
         WHERE rownum >= @curRecord and rownum < @curRecord + @batch 
    
         SET @curRecord = @curRecord + @batch 
        END 
    
    DROP TABLE #Temp 
    
    +0

    週一將試穿 –

    +0

    這個比我的方法快。 –

    相關問題