禁用 SQL 游標,告訴你外面聽不到的原因

  • 2019 年 12 月 25 日
  • 筆記

(圖拍攝於辦公室落地窗口)

周六清晨,東方剛剛露白。

L 早早來到辦公室,捎帶上最愛的熱焦瑪。今天會是一場苦戰,計划了兩個月的上線產品,今天發布。他需要極其敏捷的腦子。另外,只要 L 還在喝著咖啡,說明 DB 肯定是沒問題的,也能起到一點安慰軍心的作用吧。所以大事件面前,L 總是拿著星巴克晃悠。誰都猜不到他到底是愛喝,還是臭顯擺。

F 晃著小腦袋過來了,Release 已經開始了 1 小時,按理 DB 部分部署早該完成。這次稍微超過 L 的預期,但沒有告警,大家也就沒有太放心上。直到 F 過來找 L, 低頭問了下:

「L, 有段更新數字的腳本,跑了40多分鐘還沒結束。理論上只有100多萬數據會被更新,花這麼長時間,不知道是否正常?」

F 是個五年陳了,該經歷的也都經歷了,如今冒出這麼個疑問,L 也是慎重起來。「哪段腳本?」

SET NOCOUNT ON ;      DECLARE @SalesQuotaKey Bigint      DECLARE MY_Cur Cursor For  SELECT TOP 1000000 SalesQuotaKey  FROM FactSalesQuotaAudit  WHERE SalesAmountQuota<500000  ORDER BY SalesAmountQuota ASC      OPEN MY_Cur  FETCH NEXT FROM MY_Cur INTO @SalesQuotaKey      WHILE(@@FETCH_STATUS = 0 )  BEGIN  UPDATE FactSalesQuotaAudit  SET SalesAmountQuota = SalesAmountQuota + 100000          WHERE SalesQuotaKey = @SalesQuotaKey    FETCH NEXT FROM MY_Cur INTO @SalesQuotaKey  END      CLOSE MY_Cur  DEALLOCATE MY_Cur

「嗯,這段貌似會有問題,就看索引是怎麼建的」L 常說,trouble shooting 就像是做偵探,有時候,話其實是說給自己聽的,「如果在 SalesAmountQuota 上加索引的話,這就有危險」

「果不其然」,L打開 SSMS窗口,找到了索引定義:

CREATE Unique CLUSTERED  index PK_SalesQuotaKey  ON FactSalesQuotaAudit(SalesQuotaKey)    CREATE INDEX IDX_SALES_AMT_QUTA  ON FactSalesQuotaAudit(SalesAmountQuota)

為保分析無誤,L 還是先看了下現狀:

SELECT TOP 1000000 SalesQuotaKey  FROM FactSalesQuotaAudit  WHERE SalesAmountQuota<500000  ORDER BY SalesAmountQuota ASC

「目前來看,這段腳本還在繼續跑著」

「但執行計劃顯示正確跑了 SalesAmountQuota 的索引呢?」F 不解

「其實這裡真是這個索引惹的禍」

「索引是用到了,但是每次更新,更新的那行跑到 IDX_SALES_AMT_QUTA 索引後面去了,導致無限在更新 SalesAmountQuota 的值,直到大於 50萬」L 覺得平時太強調 seek 索引了,但沒有全面透徹的講解索引其實也有好心辦壞事兒的時候。所以索引要給 F 畫個腦圖:

「更新完的數據又排回索引了,而游標一直在往前讀滿足條件的數據,你可以細想下這個有趣的過程」看到 F 頻頻點頭,L 自以為已經講的很明晰了。

"終於跑完了," F 眼見監控 Dashboard 上的那個超長 session 消失了,臉色也開始和悅起來。

「大錯即將發生」L 一盆冷水澆過去,F 又不惑,90後小姑娘的臉色,真是跟天氣一樣,瞬間都能千變萬化。

SELECT COUNT(*)  FROM FactSalesQuotaAudit WITH(NOLOCK)  WHERE SalesAmountQuota<500000

「你看,結果是0,肯定不是你想要的結果吧。你原意肯定是在不滿50萬額度的那些銷售上,再加十萬,現在全部都加到了50萬。這是典型的 Halloween 問題」

「那,怎麼辦?」F 面對這段讓她面紅耳赤的游標,簡直奔潰

「用臨時表,先把數據更新對了,再找最優解決方法」