SQL Server--存在則更新問題

来源:http://www.cnblogs.com/TeyGao/archive/2017/06/01/6929246.html
-Advertisement-
Play Games

在博客園看到一篇討論特別多的文章“探討SQL Server併發處理存在就更新七種解決方案”,這種業務需求很常見:如果記錄存在就更新,不存在就插入。 最常見的做法: 一個很明顯的問題,在高併發下可能存在操作同一條記錄的多個線程都進入到INSERT環節,導致插入失敗。 上面問題原因在於進入INSERT或 ...


在博客園看到一篇討論特別多的文章“探討SQL Server併發處理存在就更新七種解決方案”,這種業務需求很常見:如果記錄存在就更新,不存在就插入。

最常見的做法:

BEGIN TRANSACTION
IF EXISTS ( SELECT  1
            FROM    Test
            WHERE   Id = @Id )
    UPDATE  Test
    SET     [Counter] = [Counter] + 1
    WHERE   Id = @Id;
ELSE
    INSERT  Test
            ( Id, Name, [Counter] )
    VALUES  ( @Id, @Name, 1 );
COMMIT

一個很明顯的問題,在高併發下可能存在操作同一條記錄的多個線程都進入到INSERT環節,導致插入失敗。

上面問題原因在於進入INSERT或UPDATE環節沒有“排他”鎖,如果每個線程在進行插入或更新前就獲得記錄的“排他鎖”,也就解決了其他線程併發處理相同記錄的可能性。換個說法,一個蘿蔔一個坑,先不管這個坑有沒有蘿蔔,先把坑占上,再考慮其他的。

如何占坑呢?而且是“排他地”占坑呢?

在SQL SERVER中,排他鎖即X鎖,對目標加X鎖有兩種方式:

1、使用SELECT+WITH(XLOCK)查詢提示

2、使用UPDATE/INSERT/DELETE操作

雖然SELECT+WITH(XLOCK)查詢提示能做到加X鎖,但是這種X鎖有點“不靠譜”,MSDN給出解釋:

Using XLOCK in SELECT statements will not prevent reads from happening. This is because SQL Server has a special optimization under read committed isolation level that checks if the row is dirty or not and ignores the xlock if the row has not changed. Since this is acceptable under the read committed isolation level semantics it is by design. 

哪就只能UPDATE/INSERT/DELETE方式,DELETE肯定排除,直接INSERT如果碰到記錄已存在又會報錯,最終只能選擇UPDATE,於是將業務需求實現為:

BEGIN TRANSACTION
--先嘗試更新記錄占坑
UPDATE  Test
SET     [Counter] = [Counter] + 1
WHERE   Id = @Id;

--如果更新操作沒有影響行,證明記錄不存在,則插入
IF @@ROWCOUNT<1
BEGIN
    INSERT  Test
            ( Id, Name, [Counter] )
    VALUES  ( @Id, @Name, 1 );
END
COMMIT

哪這樣真的把坑占上沒?

當ID=1記錄不存在時,執行下麵SQL:

BEGIN TRANSACTION
--先嘗試更新記錄占坑
UPDATE  Test
SET     [Counter] = [Counter] + 1
WHERE   Id = 1;

EXEC sp_lock @@SPID

雖然UPDATE的確會產生X排他鎖,但是沒有把鎖“持續”地占下來,因此也無法保證高併發下對該記錄的INSERT/UPDATE操作以“串列”方式執行。

要“持續”鎖,也有兩個辦法:

1、使用WITH(HOLDLOCK)鎖提示

2、使用SERIALIZABLE事務隔離級別

看下使用WITH(HOLDLOCK)鎖提示獲得的鎖,同樣當ID=1記錄不存在時:

BEGIN TRANSACTION
--先嘗試更新記錄占坑
UPDATE  Test WITH(HOLDLOCK)
SET     [Counter] = [Counter] + 1
WHERE   Id = 1;

EXEC sp_lock @@SPID

可以看到除對錶和唯一索引上加IX和IS鎖以及頁上IX鎖外,還有一個KEY級別的範圍鎖RangX-X,由於範圍X鎖的存在,任何其他回話嘗試對此範圍的UPDATE和INSERT操作都將被阻塞,因此可以繼續判斷是否需要插入。

當然,使用SERIALIZABLE隔離級別也是相同的效果

BEGIN TRANSACTION
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
--先嘗試更新記錄占坑
UPDATE  Test
SET     [Counter] = [Counter] + 1
WHERE   Id = 1;

EXEC sp_lock @@SPID

======================================

更新後如何判斷是否需要插入呢?

方式1: 使用@@ROWCOUNT來判斷更新數據是否影響行,如果影響,則證明數據存在,無需更新

方式2: 使用IF NOT EXISTS也是可以的,反正坑已經被占上,別的回話也不能改,再查一次就是畫蛇添足而已,不影響結果。

總的還是推薦使用方式1,效率最高,避免一次SELECT操作,最後的腳本推薦為:

BEGIN TRANSACTION
--先嘗試更新記錄占坑
UPDATE  Test WITH(HOLDLOCK)
SET     [Counter] = [Counter] + 1
WHERE   Id = @Id;

--如果更新操作沒有影響行,證明記錄不存在,則插入
IF @@ROWCOUNT<1
BEGIN
    INSERT  Test
            ( Id, Name, [Counter] )
    VALUES  ( @Id, @Name, 1 );
END
COMMIT

================================================

PS1: 通常情況下,個人不太推薦修改事務隔離級別,事務隔離級別影響的是整個事務,而鎖提示隻影響特定語句。

================================================

PS2: 上面業務查詢和更新都基於主鍵,在很多真實的業務場景下,主鍵通常為非業務鍵即自增鍵,而需要根據業務鍵來操作,存在以下死鎖可能:

回話1: 先獲取聚集索引上X鎖,嘗試獲取非聚集索引上的X鎖

回話2:先獲取到非聚集索引上的X鎖,嘗試獲取聚集索引上X鎖

=================================================

語文不好,本來很簡單的一個東西,被自己描述成這樣,各位包含。

處理問題,先看原理,再考慮如何解決,才最簡單有效。

=================================================

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 今天使用find命令查找刪除文件時,遇到下麵錯誤,這個是因為在{}和\之間必須要有空格,否則會報上面的錯。 以前都沒有註意到這個細節,特此記錄一下。 [root@DB-Server full]# find . -maxdepth 1 -type d -mtime +3 -exec rm -rf {} ...
  • SELECT * FROM (SELECT a.*,rownum row_num FROM (SELECT etpcode,etpename FROM tetp c ORDER BY c.etpcode DESC ) a ) b WHERE b.row_num BETWEEN 1 AND 2 ...
  • 最近在編寫一個小型基於的jsp系統開發。掌握資料庫一直感覺還不錯。但是今天就出現了一個問題困擾我大半天。後來本來準備睡覺,但是覺得今天不解決這個問題恐怕晚上是“徹夜難眠啊”!!於是打開電腦,又開始搗騰。遇到問題首先去網上search了一下。但是大多數的結果只能說:只能遠看而不能解決我的問題。 首先我 ...
  • ORACLE體繫結構包括:實例(Instance),資料庫文件,用戶進程(User process),伺服器進程以及其他文件。 1.ORACLE實例(instance) 1).要訪問資料庫必須先啟動實例,實例啟動時先分配記憶體區,然後再啟動後臺進程,後他進程執行資料庫的輸入,輸出以及監控其他進程。 在 ...
  • 今天在虛擬機上掛載光碟時提示: [root@primary dev]# mount /dev/cdrom /mnt/cdrom mount: you must specify the filesystem type 處理方法: 虛擬機-〉setting->cd/dvd-〉device status- ...
  • 獲得資料庫和表的信息 一般正常的程式員或DBA都會在敲代碼的時候突然想到這樣的一系列問題:我是誰?我在哪?我在乾什麼? 我的資料庫呢?我的表呢?我表怎麼創建的?我該怎麼辦呢?你可能會想到SHOW DATABASES; 命令。But, 這個命令是列出由mysql管理的databases. 不是知道我再 ...
  • 溫習《高性能MySQL》的第一章 MySQL架構與歷史 1.1 MySQL邏輯架構 參考http://www.cnblogs.com/baochuan/archive/2012/03/15/2397536.html 圖1-1:MySQL伺服器邏輯架構圖 最上層的服務並不是MySQL所獨有的,大多數基 ...
  • 提取資料庫所有表的表名、欄位名 在SQLserver 2000中測試 在SQLserver 2005中測試 提取某個表的欄位名 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...