1. 程式人生 > 其它 >SQL Server 中WITH (NOLOCK)淺析 sql語句對資料庫表進行加鎖和解鎖

SQL Server 中WITH (NOLOCK)淺析 sql語句對資料庫表進行加鎖和解鎖

概念介紹

  

開發人員喜歡在SQL指令碼中使用WITH(NOLOCK), WITH(NOLOCK)其實是表提示(table_hint)中的一種。它等同於 READUNCOMMITTED 。 具體的功能作用如下所示(摘自MSDN):

   1: 指定允許髒讀。不釋出共享鎖來阻止其他事務修改當前事務讀取的資料,其他事務設定的排他鎖不會阻礙當前事務讀取鎖定資料。允許髒讀可能產生較多的併發操作,但其代價是讀取以後會被其他事務回滾的資料修改。這可能會使您的事務出錯,向用戶顯示從未提交過的資料,或者導致使用者兩次看到記錄(或根本看不到記錄)。有關髒讀、不可重複讀和幻讀的詳細資訊,請參閱併發影響

   2: READUNCOMMITTED 和 NOLOCK 提示僅適用於資料鎖。所有查詢(包括那些帶有 READUNCOMMITTED 和 NOLOCK 提示的查詢)都會在編譯和執行過程中獲取 Sch-S(架構穩定性)鎖。因此,當併發事務持有表的 Sch-M(架構修改)鎖時,將阻塞查詢。例如,資料定義語言 (DDL) 操作在修改表的架構資訊之前獲取 Sch-M 鎖。所有併發查詢(包括那些使用 READUNCOMMITTED 或 NOLOCK 提示執行的查詢)都會在嘗試獲取 Sch-S 鎖時被阻塞。相反,持有 Sch-S 鎖的查詢將阻塞嘗試獲取 Sch-M 鎖的併發事務。有關鎖行為的詳細資訊,請參閱

鎖相容性(資料庫引擎)

   3:  不能為通過插入、更新或刪除操作修改過的表指定 READUNCOMMITTED 和 NOLOCK。SQL Server 查詢優化器忽略 FROM 子句中應用於 UPDATE 或 DELETE 語句的目標表的 READUNCOMMITTED 和 NOLOCK 提示。

 

功能與缺陷

 

    使用WIHT(NOLOCK)有利也有弊,所以在決定使用之前,你一定需要了解清楚WITH(NOLOCK)的功能和缺陷,看其是否適合你的業務需求,不要覺得它能提升效能,稀裡糊塗的就使用它。

 

    1:使用WITH(NOLOCK)時查詢不受其它排他鎖阻塞

    開啟會話視窗1,執行下面指令碼,不提交也不回滾事務,模擬事務真在執行過程當中

BEGIN TRAN
 
       UPDATE TEST SET NAME='Timmy' WHERE OBJECT_ID =1;
 
       --ROLLBACK
 

   

   開啟會話視窗2,執行下面指令碼,你會發現執行結果一直查詢不出來(其實才兩條記錄)。當前會話被阻塞了

SELECT * FROM TEST;

    開啟會話視窗3,執行下面指令碼,檢視阻塞情況,你會發現在會話2被會話1給阻塞了,會話2的等待型別為LCK_M_S:“當某任務正在等待獲取共享鎖時出現”

 
 
  SELECT wt.blocking_session_id                    AS BlockingSessesionId
        ,sp.program_name                           AS ProgramName
        ,COALESCE(sp.LOGINAME, sp.nt_username)     AS HostName    
        ,ec1.client_net_address                    AS ClientIpAddress
        ,db.name                                   AS DatabaseName        
        ,wt.wait_type                              AS WaitType                    
        ,ec1.connect_time                          AS BlockingStartTime
        ,wt.WAIT_DURATION_MS/1000                  AS WaitDuration
        ,ec1.session_id                            AS BlockedSessionId
        ,h1.TEXT                                   AS BlockedSQLText
        ,h2.TEXT                                   AS BlockingSQLText
  FROM sys.dm_tran_locks AS tl
  INNER JOIN sys.databases db
    ON db.database_id = tl.resource_database_id
  INNER JOIN sys.dm_os_waiting_tasks AS wt
    ON tl.lock_owner_address = wt.resource_address
  INNER JOIN sys.dm_exec_connections ec1
    ON ec1.session_id = tl.request_session_id
  INNER JOIN sys.dm_exec_connections ec2
    ON ec2.session_id = wt.blocking_session_id
  LEFT OUTER JOIN master.dbo.sysprocesses sp
    ON SP.spid = wt.blocking_session_id
  CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1
  CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2

 

 

 

此時檢視會話1(會話1的會話ID為53,執行指令碼1前,可以用SELECT  @@spid檢視會話ID)的鎖資訊情況,你會發現表TEST(ObjId=1893581784)持有的鎖資訊如下所示

 

   

開啟會話視窗4,執行下面指令碼.你會發現查詢結果很快就出來,會話4並不會被會話1阻塞。

    SELECT * FROM TEST WITH(NOLOCK)

從上面模擬的這個小例子可以看出,正是由於加上WITH(NOLOCK)提示後,會話1中事務設定的排他鎖不會阻礙當前事務讀取鎖定資料,所以會話4不會被阻塞,從而提升併發時查詢效能。

 

2:WITH(NOLOCK) 不釋出共享鎖來阻止其他事務修改當前事務讀取的資料,這個就不舉例子了。

本質上WITH(NOLOCK)是通過減少鎖和不受排它鎖影響來減少阻塞,從而提高併發時的效能。所謂凡事有利也有弊,WITH(NOLOCK)在提升效能的同時,也會產生髒讀現象。

如下所示,表TEST有兩條記錄,我準備更新OBJECT_ID=1的記錄,此時事務既沒有提交也沒有回滾

BEGIN TRAN 
 
UPDATE TEST SET NAME='Timmy' WHERE OBJECT_ID =1; 
 
--ROLLBACK 
 

此時另外一個會話使用WITH(NOLOCK)查到的記錄為未提交的記錄值

假如由於某種原因,該事務回滾了,那麼我們讀取到的OBJECT_ID=1的記錄就是一條髒資料。

髒讀又稱無效資料的讀出,是指在資料庫訪問中,事務T1將某一值修改,然後事務T2讀取該值,此後T1因為某種原因撤銷對該值的修改,這就導致了T2所讀取到的資料是無效的。

 

WITH(NOLOCK)使用場景

 

什麼時候可以使用WITH(NOLOCK)? 什麼時候不能使用WITH(NOLOCK),這個要視你係統業務情況,綜合考慮效能情況與業務要求來決定是否使用WITH(NOLOCK), 例如涉及到金融或會計成本之類的系統,出現髒讀那是要產生嚴重問題的。關鍵業務系統也要慎重考慮。大體來說一般有下面一些場景可以使用WITH(NOLOCK)

   1: 基礎資料表,這些表的資料很少變更。

   2:歷史資料表,這些表的資料很少變更。

   3:業務允許髒讀情況出現涉及的表。

   4:資料量超大的表,出於效能考慮,而允許髒讀。

另外一點就是不要濫用WITH(NOLOCK),我發現有個奇怪現象,很多開發知道WITH(NOLOCK),但是有不瞭解髒讀,習慣性的使用WITH(NOLOCK)。

 

WITH(NOLOCK)與 NOLOCK區別

 

為了搞清楚WITH(NOLOCK)與NOLOCK的區別,我查了大量的資料,我們先看看下面三個SQL語句有啥區別

    SELECT * FROM TEST NOLOCK

    SELECT * FROM TEST (NOLOCK);

    SELECT * FROM TEST WITH(NOLOCK);

上面的問題概括起來也就是說NOLOCK、(NOLOCK)、 WITH(NOLOCK)的區別:

1: NOLOCK這樣的寫法,其實NOLOCK其實只是別名的作用,而沒有任何實質作用。所以不要粗心將(NOLOCK)寫成NOLOCK

2:(NOLOCK)與WITH(NOLOCK)其實功能上是一樣的。(NOLOCK)只是WITH(NOLOCK)的別名,但是在SQL Server 2008及以後版本中,(NOLOCK)不推薦使用了,"不借助 WITH 關鍵字指定表提示”的寫法已經過時了。 具體參見MSDN http://msdn.microsoft.com/zh-cn/library/ms143729%28SQL.100%29.aspx

    2.1  至於網上說WITH(NOLOCK)在SQL SERVER 2000不生效,我驗證後發現完全是個謬論。

    2.2  在使用連結伺服器的SQL當中,(NOLOCK)不會生效,WITH(NOLOCK)才會生效。如下所示

    訊息 4122,級別 16,狀態 1,第 1 行

    Remote table-valued function calls are not allowed.

 

3.語法上有些許出入,如下所示

這種語法會報錯
SELECT  * FROM   sys.indexes  WITH(NOLOCK) AS i
-Msg 156, Level 15, State 1, Line 1
-Incorrect syntax near the keyword 'AS'.
 
這種語法正常
SELECT  * FROM   sys.indexes  (NOLOCK) AS i
 
可以全部改寫為下面語法
 
SELECT  * FROM   sys.indexes   i WITH(NOLOCK) 
 
 
SELECT  * FROM   sys.indexes   i (NOLOCK) 

 

WITH(NOLOCK)會不會產生鎖

    很多人誤以為使用了WITH(NOLOCK)後,資料庫庫不會產生任何鎖。實質上,使用了WITH(NOLOCK)後,資料庫依然對該表物件生成Sch-S(架構穩定性)鎖以及DB型別的共享鎖, 如下所示,可以在一個會話中查詢一個大表,然後在另外一個會話中檢視鎖資訊(也可以使用SQL Profile檢視會話鎖資訊)

    不使用WTIH(NOLOCK)

  使用WITH(NOLOCK)

  從上可以看出使用WITH(NOLOCK)後,資料庫並不是不生成相關鎖。  對比可以發現使用WITH(NOLOCK)後,資料庫只會生成DB型別的共享鎖、以及TAB型別的架構穩定性鎖.

另外,使用WITH(NOLOCK)並不是說就不會被其它會話阻塞,依然可能會產生Schema Change Blocking

會話1:執行下面SQL語句,暫時不提交,模擬事務正在執行

BEGIN TRAN 
 
  ALTER TABLE TEST ADD Grade VARCHAR(10) ; 
 

會話2:執行下面語句,你會發現會話被阻塞,截圖如下所示。

SELECT * FROM TEST WITH(NOLOCK)

 

sql語句對資料庫表進行加鎖和解鎖

 

鎖是資料庫中的一個非常重要的概念,它主要用於多使用者環境下保證資料庫完整性和一致性。 我們知道,多個使用者能夠同時操縱同一個資料庫中的資料,會發生資料不一致現象。即如果沒有鎖定且多個使用者同時訪問一個數據庫,則當他們的事務同時使用相同的資料時可能會發生問題。這些問題包括:丟失更新、髒讀、不可重複讀和幻覺讀:

1.丟失更新:

當兩個或多個事務選擇同一行,然後基於最初選定的值更新該行時,會發生丟失更新問題。每個事務都不知道其它事務的存在。最後的更新將重寫由其它事務所做的更新,這將導致資料丟失。例如,兩個編輯人員製作了同一文件的電子複本。每個編輯人員獨立地更改其複本,然後儲存更改後的複本,這樣就覆蓋了原始文件。最後儲存其更改複本的編輯人員覆蓋了第一個編輯人員所做的更改。如果在第一個編輯人員完成之後第二個編輯人員才能進行更改,則可以避免該問題。

2.髒讀

髒讀就是指當一個事務正在訪問資料,並且對資料進行了修改,而這種修改還沒有提交到資料庫中,這時,另外一個事務也訪問這個資料,然後使用了這個資料。因為這個資料是還沒有提交的資料,那麼另外一個事務讀到的這個資料是髒資料,依據髒資料所做的操作可能是不正確的。例如,一個編輯人員正在更改電子文件。在更改過程中,另一個編輯人員複製了該文件(該複本包含到目前為止所做的全部更改)並將其分發給預期的使用者。此後,第一個編輯人員認為目前所做的更改是錯誤的,於是刪除了所做的編輯並儲存了文件。分發給使用者的文件包含不再存在的編輯內容,並且這些編輯內容應認為從未存在過。如果在第一個編輯人員確定最終更改前任何人都不能讀取更改的文件,則可以避免該問題。

3.不可重複讀

不可重複讀是指在一個事務內,多次讀同一資料。在這個事務還沒有結束時,另外一個事務也訪問該同一資料。那麼,在第一個事務中的兩次讀資料之間,由於第二個事務的修改,那麼第一個事務兩次讀到的的資料可能是不一樣的。這樣就發生了在一個事務內兩次讀到的資料是不一樣的,因此稱為是不可重複讀。例如,一個編輯人員兩次讀取同一文件,但在兩次讀取之間,作者重寫了該文件。當編輯人員第二次讀取文件時,文件已更改。原始讀取不可重複。如果只有在作者全部完成編寫後編輯人員才可以讀取文件,則可以避免該問題。

4.幻覺讀

幻覺讀是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的資料進行了修改,這種修改涉及到表中的全部資料行。同時,第二個事務也修改這個表中的資料,這種修改是向表中插入一行新資料。那麼,以後就會發生操作第一個事務的使用者發現表中還有沒有修改的資料行,就好象發生了幻覺一樣。例如,一個編輯人員更改作者提交的文件,但當生產部門將其更改內容合併到該文件的主複本時,發現作者已將未編輯的新材料新增到該文件中。如果在編輯人員和生產部門完成對原始文件的處理之前,任何人都不能將新材料新增到文件中,則可以避免該問題 

 

SELECT 語句中“加鎖選項”的功能說明

SQL Server提供了強大而完備的鎖機制來幫助實現資料庫系統的併發性和高效能。使用者既能使用SQL Server的預設設定也可以在select 語句中使用“加鎖選項”來實現預期的效果

1. NOLOCK(不加鎖)

此選項被選中時,SQL Server 在讀取或修改資料時不加任何鎖。 在這種情況下,使用者有可能讀取到未完成事務(Uncommited Transaction)或回滾(Roll Back)中的資料, 即所謂的“髒資料”。

2. HOLDLOCK(保持鎖)

此選項被選中時,SQL Server 會將此共享鎖保持至整個事務結束,而不會在途中釋放。

SELECT * FROM table WITH (HOLDLOCK)
其他事務可以讀取表,但不能更新刪除

3. UPDLOCK(修改鎖)

此選項被選中時,SQL Server 在讀取資料時使用修改鎖來代替共享鎖,並將此鎖保持至整個事務或命令結束。使用此選項能夠保證多個程序能同時讀取資料但只有該程序能修改資料。

UPDLOCK.UPDLOCK 的優點是允許您讀取資料(不阻塞其它事務)並在以後更新資料,同時確保自從上次讀取資料後資料沒有被更改。當我們用UPDLOCK來讀取記錄時可以對取到的記錄加上更新鎖,從而加上鎖的記錄在其它的執行緒中是不能更改的只能等本執行緒的事務結束後才能更改.

示例:

在另一個查詢裡:
BEGIN TRANSACTION
SELECT * FROM myTable WITH (UPDLOCK) WHERE Id in (1,2,3)
waitfor delay '00:00:10' 
update myTable set [Name]='ZZ' where Id in (1,2,3)
commit TRANSACTION

在另一個查詢裡:
SELECT * FROM myTable WHERE Id in (1,2,3)
可以馬上查詢到資料。

但如果要更新資料,必須等其他更新鎖釋放後才能執行。
update myTable set [Name]='ZZ' where Id in (1,2,3)

這就說明,有時候需要控制某條記錄在我讀取後就不許再進行更新,那麼我就可以將所有要處理當前記錄的查詢都加上更新鎖,以防止查詢後被其它事務修改。將事務的影響降低到最小

4. TABLOCK(表鎖)

此選項被選中時,SQL Server 將在整個表上置共享鎖直至該命令結束。 這個選項保證其他程序只能讀取而不能修改資料。

5. TABLOCKX(排它表鎖)

此選項被選中時,SQL Server 將在整個表上置排它鎖直至該命令或事務結束。這將防止其他程序讀取或修改表中的資料。

SELECT * FROM table WITH (TABLOCKX)

其他事務不能讀取表,更新和刪除

6. PAGLOCK(頁鎖)

此選項為預設選項, 當被選中時,SQL Server 使用共享頁鎖。

7. ROWLOCK (強制使用行鎖) 

一直有個疑問,使用 select * from dbo.A with(RowLock) WHRE a=1 這樣的語句,系統是什麼時候釋放行鎖呢??
經過官方文件考證後,原來 RowLock在不使用組合的情況下是沒有任何意義的,所謂“解鈴還須繫鈴人~”
With(RowLock,UpdLock) 這樣的組合才成立,查詢出來的資料使用RowLock來鎖定,當資料被Update的時候,或者回滾之後,鎖將被釋放 

鎖是資料庫中的一個非常重要的概念,它主要用於多使用者環境下保證資料庫完整性和一致性。 我們知道,多個使用者能夠同時操縱同一個資料庫中的資料,會發生資料不一致現象。即如果沒有鎖定且多個使用者同時訪問一個數據庫,則當他們的事務同時使用相同的資料時可能會發生問題。這些問題包括:丟失更新、髒讀、不可重複讀和幻覺讀:

1.丟失更新:

當兩個或多個事務選擇同一行,然後基於最初選定的值更新該行時,會發生丟失更新問題。每個事務都不知道其它事務的存在。最後的更新將重寫由其它事務所做的更新,這將導致資料丟失。例如,兩個編輯人員製作了同一文件的電子複本。每個編輯人員獨立地更改其複本,然後儲存更改後的複本,這樣就覆蓋了原始文件。最後儲存其更改複本的編輯人員覆蓋了第一個編輯人員所做的更改。如果在第一個編輯人員完成之後第二個編輯人員才能進行更改,則可以避免該問題。

2.髒讀

髒讀就是指當一個事務正在訪問資料,並且對資料進行了修改,而這種修改還沒有提交到資料庫中,這時,另外一個事務也訪問這個資料,然後使用了這個資料。因為這個資料是還沒有提交的資料,那麼另外一個事務讀到的這個資料是髒資料,依據髒資料所做的操作可能是不正確的。例如,一個編輯人員正在更改電子文件。在更改過程中,另一個編輯人員複製了該文件(該複本包含到目前為止所做的全部更改)並將其分發給預期的使用者。此後,第一個編輯人員認為目前所做的更改是錯誤的,於是刪除了所做的編輯並儲存了文件。分發給使用者的文件包含不再存在的編輯內容,並且這些編輯內容應認為從未存在過。如果在第一個編輯人員確定最終更改前任何人都不能讀取更改的文件,則可以避免該問題。

3.不可重複讀

不可重複讀是指在一個事務內,多次讀同一資料。在這個事務還沒有結束時,另外一個事務也訪問該同一資料。那麼,在第一個事務中的兩次讀資料之間,由於第二個事務的修改,那麼第一個事務兩次讀到的的資料可能是不一樣的。這樣就發生了在一個事務內兩次讀到的資料是不一樣的,因此稱為是不可重複讀。例如,一個編輯人員兩次讀取同一文件,但在兩次讀取之間,作者重寫了該文件。當編輯人員第二次讀取文件時,文件已更改。原始讀取不可重複。如果只有在作者全部完成編寫後編輯人員才可以讀取文件,則可以避免該問題。

4.幻覺讀

幻覺讀是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的資料進行了修改,這種修改涉及到表中的全部資料行。同時,第二個事務也修改這個表中的資料,這種修改是向表中插入一行新資料。那麼,以後就會發生操作第一個事務的使用者發現表中還有沒有修改的資料行,就好象發生了幻覺一樣。例如,一個編輯人員更改作者提交的文件,但當生產部門將其更改內容合併到該文件的主複本時,發現作者已將未編輯的新材料新增到該文件中。如果在編輯人員和生產部門完成對原始文件的處理之前,任何人都不能將新材料新增到文件中,則可以避免該問題 

 

SELECT 語句中“加鎖選項”的功能說明

SQL Server提供了強大而完備的鎖機制來幫助實現資料庫系統的併發性和高效能。使用者既能使用SQL Server的預設設定也可以在select 語句中使用“加鎖選項”來實現預期的效果

1. NOLOCK(不加鎖)

此選項被選中時,SQL Server 在讀取或修改資料時不加任何鎖。 在這種情況下,使用者有可能讀取到未完成事務(Uncommited Transaction)或回滾(Roll Back)中的資料, 即所謂的“髒資料”。

2. HOLDLOCK(保持鎖)

此選項被選中時,SQL Server 會將此共享鎖保持至整個事務結束,而不會在途中釋放。

SELECT * FROM table WITH (HOLDLOCK)
其他事務可以讀取表,但不能更新刪除

3. UPDLOCK(修改鎖)

此選項被選中時,SQL Server 在讀取資料時使用修改鎖來代替共享鎖,並將此鎖保持至整個事務或命令結束。使用此選項能夠保證多個程序能同時讀取資料但只有該程序能修改資料。

UPDLOCK.UPDLOCK 的優點是允許您讀取資料(不阻塞其它事務)並在以後更新資料,同時確保自從上次讀取資料後資料沒有被更改。當我們用UPDLOCK來讀取記錄時可以對取到的記錄加上更新鎖,從而加上鎖的記錄在其它的執行緒中是不能更改的只能等本執行緒的事務結束後才能更改.

示例:

在另一個查詢裡:
BEGIN TRANSACTION
SELECT * FROM myTable WITH (UPDLOCK) WHERE Id in (1,2,3)
waitfor delay '00:00:10' 
update myTable set [Name]='ZZ' where Id in (1,2,3)
commit TRANSACTION

在另一個查詢裡:
SELECT * FROM myTable WHERE Id in (1,2,3)
可以馬上查詢到資料。

但如果要更新資料,必須等其他更新鎖釋放後才能執行。
update myTable set [Name]='ZZ' where Id in (1,2,3)

這就說明,有時候需要控制某條記錄在我讀取後就不許再進行更新,那麼我就可以將所有要處理當前記錄的查詢都加上更新鎖,以防止查詢後被其它事務修改。將事務的影響降低到最小

4. TABLOCK(表鎖)

此選項被選中時,SQL Server 將在整個表上置共享鎖直至該命令結束。 這個選項保證其他程序只能讀取而不能修改資料。

5. TABLOCKX(排它表鎖)

此選項被選中時,SQL Server 將在整個表上置排它鎖直至該命令或事務結束。這將防止其他程序讀取或修改表中的資料。

SELECT * FROM table WITH (TABLOCKX)

其他事務不能讀取表,更新和刪除

6. PAGLOCK(頁鎖)

此選項為預設選項, 當被選中時,SQL Server 使用共享頁鎖。

7. ROWLOCK (強制使用行鎖) 

一直有個疑問,使用 select * from dbo.A with(RowLock) WHRE a=1 這樣的語句,系統是什麼時候釋放行鎖呢??
經過官方文件考證後,原來 RowLock在不使用組合的情況下是沒有任何意義的,所謂“解鈴還須繫鈴人~”
With(RowLock,UpdLock) 這樣的組合才成立,查詢出來的資料使用RowLock來鎖定,當資料被Update的時候,或者回滾之後,鎖將被釋放