前言
假如要判斷資料是否存在於資料表中,存在就更新,不存在就新增.
這時我們可以使用Merge
來幫助我們完成.
當兩個資料表有複雜的比對的特性時,
MERGE
陳述式的條件式行為表現最佳。
有了Merge
我們就不用使用IF EXISTS
.
一切都是這麼完美…
直到到有一天Merge
在Prod撞到一個問題..
問題描述
使用語法user defined table type & Table如下
1 |
|
呼叫執行SP[dbo].[CalculateLimitation]
1 | CREATE OR ALTER PROC [dbo].[CalculateStake] |
主要傳入參數判斷更新或新增[dbo].[PriceLimitation]
表
我們在ELK
發現在執行SP時很少概率會發生PRIMARY KEY重複問題.
Violation of PRIMARY KEY constraint ‘PK_PriceLimitation’. Cannot insert duplicate key in object ‘dbo.PriceLimitation’.
但這張表PK使用欄位都有正確在Merge
條件上,所以當下我嘗試在DEV重現此問題,但一直無法成功
後來發現此問題在高併發時才會發生,所以我們使用Query Stress來幫助我們模擬高併發請求時的狀態.
Query Stress重現問題
撰寫了模擬SQL並利用Query Stress重現問題.
模擬SQL腳本
1 | DECLARE @CategoryID int, |
我使用模擬參數是
- Iterator:30
- Thread:100
來模擬高併發時資料庫請求狀況,就能發現這時已經會出現Prod的Exception
.
找到問題尋求解法
對於目前Prod問題已經邁出一大步了,因為現在問題可以重現,在網路上找了許多文章還是沒找到解法….
後面在MSDN時看到關鍵一段話,關於merge-transact-sql.
請務必只從目標資料表指定用於比對用途的資料行。 也就是說,從目標資料表中指定要與來源資料表的對應資料行進行比較的資料行。 請勿嘗試在 ON 子句中篩選出目標資料表的資料列 (例如指定
AND NOT target_table.column_x = value)
來改善查詢效能。 這樣做可能會傳回非預期且不正確的結果。
後面有看到有篇文章在介紹use-where-clause-with-merge
我就嘗試把sp寫法改成只利用兩個Table可以JOIN
欄位當作條件,發現Duplicate PK問題就可以解決了….但發現另一個更麻煩問題.
SP改寫後
1 | CREATE OR ALTER PROC [dbo].[CalculateStake] |
新寫法的執行計畫在對於大資料表時會很沒效率….
改寫後遇到的問題(不好的執行計畫)
一般SP在執行過後都會把使用的執行計畫快取起來,所以我們可以透過DMV來查看執行執行計畫.
1 | SELECT Cacheobjtype, Objtype, TEXT, query_plan |
新和舊SP寫法執行計畫如下圖.
舊寫法
新寫法
造成上面差異原因,因為新寫法透過統計資訊使用效能較差的執行計畫(能看到上面使用Merge Join
明明傳入結果集資料並不多)且在WHEN MATCHED
進行第二次判斷…
所以效能就變很差,現在已經找到此問題點了,我就在思考那有沒有辦法兼具效能又可解決此問題呢?
最終版SP寫法
最後我就思考何不如把傳入參數全部加入user defined table type
- 這樣就可以利用傳入參數當作
ON
條件也可以得到精準執行計畫. user defined table type
所有欄位可以跟Table的Clustered Index Match.
1 | drop proc[dbo].[CalculateStake] |
測試腳本改成把參數透過uftt_PriceLimit
傳入
1 | DECLARE @CategoryID int, |
請在跑修改後的SP前記得把Table先Truncate掉,這樣可以更精準模擬
使用QueryStress模擬參數
- Iterator:30
- Thread:100
執行結果如下
利用Query Stress
工具壓測發現問題解決且效能不會變差:)
小結:
沒想到Merge
在On
條件有些隱藏限制(對於On寫value condition官方只有說會有想不到的問題發生,並沒解釋原因為何…),但經過這次經驗我日後在使用Merge
時不會直接在On使用value condition會在中間多墊一層Table
這樣就可以使用ON
來JOIN
.
另外QueryStress
真是一個對於DB壓測找問題的好工具,推薦大家去了解使用
此文作者:Daniel Shih(石頭)
此文地址: https://isdaniel.github.io/sqlserver-merge-condition-problem/
版權聲明:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW 許可協議。轉載請註明出處!