[食譜好菜] 用 SqlBulkCopy 可以快速批次 Insert 大量資料,那批次 Update 大量資料呢?

先前有介紹過用 SqlBulkCopy 快速批次 Insert 大量資料,心裡想說既然快速批次 Insert 大量資料有 SqlBulkCopy,那我要快速批次 Update 大量資料有沒有類似於 SqlBulkCopy 的東西可以用?很可惜,可能是我孤陋寡聞,遍尋不著可以像 SqlBulkCopy 這麼相對容易操作的工具,不過我倒是有找到替代方案,速度上也可以接受,我把整個實作的過程做個記錄,以利往後參考。

參考的來源是這三篇文章,大家可以直接看原始文章:

在開始之前,我已經準備好一個測試資料表叫 BulkTable,裡面有兩個欄位 IdText

建立使用者定義資料表類型(User-Defined Table Types)

這是一個拿空間換取時間的做法,從 SQL Server 2008 開始有 TVP(Table Value Parameter)可以用,也就是說我們可以把 DataTable 當成參數值塞給 SQL Server,但是要採用 TVP 我們則必須先建立一個使用者定義資料表類型。

CREATE TYPE [dbo].[BulkTableType] AS TABLE
([Id]   [INT] NOT NULL,
 [Text] [NVARCHAR](MAX) NOT NULL,
 PRIMARY KEY([Id])
);

搭配 JOIN 的語法來 Update 資料

我們先把要更新的資料做成 System.Data.DataTable,然後再用 TVP 的方式把它傳遞給 SQL Server,既然 DataTable 已經被當成 Table Type 傳遞給 SQL Server 了,那麼在 SQL Server 裡面我們就可以把它當作是一般的 Table 來操作。

UPDATE [dbo].[BulkTable]
   SET [Text] = [ut].[Text]
FROM [dbo].[BulkTable] [bt]
     JOIN @UpdatedTable [ut] ON [bt].[Id] = [ut].[Id];

我的程式範例是一次操作十萬筆資料,可以看到十萬筆資料,一秒多就更新完畢了。

批次 Insert Or Update 也行

我們搭配 MERGE 語法來做到當資料存在時更新,當資料不存在時則新增。

MERGE INTO [dbo].[BulkTable] [bt]
USING @UpdatedTable [ut]
ON [bt].[Id] = [ut].[Id]
    WHEN MATCHED
    THEN UPDATE SET [Text] = [ut].[Text]
    WHEN NOT MATCHED
    THEN INSERT VALUES ([ut].[Id], [ut].[Text]);

我也是一次操作十萬筆資料,但是這十萬筆資料裡面有隨機新增的資料,也就是說一部分是新的資料,一部分是舊的資料,可以看到表現並不差,落在 2~4 秒之間。

在參考的文章中有提到可以藉由設定 SqlDataAdapter 的 UpdateBatchSize 屬性值,來達到批次 Update 的目的,在我測試過後,效能沒有比用 TVP 的方式來的好,不如我的預期,在這邊我就不做比較了。

另外,大家可能會覺得那個跑出來的秒數好像比按鈕按下去之後等待的時間還要少,那是因為我沒有把我產生測試資料的時間算進去,單純只有計算連線到 SQL Server 執行指令的時間。

 < Source Code >

相關資源

C# 指南
ASP.NET 教學
ASP.NET MVC 指引
Azure SQL Database 教學
SQL Server 教學
Xamarin.Forms 教學