鎖不住的查詢(轉載)

最近在處理一個鎖的問題時,發現一個比較鬱悶的事,使用X鎖竟然沒法鎖住查詢,模擬這個問題,可使用以下T-SQL腳原本創建測試環境。數據庫

USE master;
GO
 
IF @@TRANCOUNT > 0
    ROLLBACK TRAN;
GO
 
-- =======================================
-- 創建測試數據庫
-- a. 刪除測試庫, 若是已經存在的話
IF DB_ID(N'db_xlock_test') IS NOT NULL
BEGIN;
    ALTER DATABASE db_xlock_test
    SET SINGLE_USER
    WITH
       ROLLBACK AFTER 0;
       
    DROP DATABASE db_xlock_test;
END;
 
-- b. 創建測試數據庫
CREATE DATABASE db_xlock_test;
 
-- c. 關閉READ_COMMITTED_SNAPSHOT 以保持SELECT 的默認加鎖模式
ALTER DATABASE db_xlock_test
SET READ_COMMITTED_SNAPSHOT OFF;
GO
 
-- =======================================
-- 創建測試表
USE db_xlock_test;
GO
 
CREATE TABLE dbo.tb(
    id int IDENTITY
       PRIMARY KEY,
    name sysname
);
INSERT dbo.tb
SELECT TOP(50000)
    O1.name + N'.' + O2.name + N'.' + O3.name
FROM sys.objects O1 WITH(NOLOCK),
    sys.objects O2 WITH(NOLOCK),
    sys.objects O3 WITH(NOLOCK);
GO

 

而後,創建一個鏈接,執行下面的腳原本實現加鎖。併發

-- =======================================
-- 測試鏈接1 - 加鎖
BEGIN TRAN
    --測試的初衷是經過SELECT加鎖,結果發現UPDATE也鎖不住
    UPDATE dbo.tb SET name = name
    --SELECT COUNT(*) FROM dbo.tb WITH(XLOCK)
    WHERE id <= 2;
    
    SELECT
       spid = @@SPID,
       tran_count = @@TRANCOUNT,
       database_name = DB_NAME(),
       object_id = OBJECT_ID(N'dbo.tb', N'Table');
       
    -- 顯示鎖
    EXEC sp_lock @@SPID;

經過執行結果,能夠看到對象被加鎖的狀況:表級和頁級上是IX鎖,記錄上是X鎖。測試

 

而後新建一個鏈接,執行下面的T-SQL查詢,看看會否被鏈接1鎖住優化

-- =======================================
-- 測試鏈接2 - 被阻塞(在測試鏈接1 執行後執行)
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT * FROM dbo.tb
WHERE id <= 2;

上述查詢會很快返回結果,並不會被查詢1阻塞住。ui

 

按照咱們的瞭解(聯機幫助上也有說明),在READ COMMITTED事務隔離級別下,查詢使用共享鎖(S),而根據鎖的兼容級別,S鎖是與X鎖衝突的,因此正常狀況下,鏈接2的查詢須要等待鏈接1執行完成。但是測試的結果去違反了這一原則。spa

 

爲了瞭解爲何鏈接2不會被阻塞,對鏈接2作了一個Trace,發現一個更鬱悶的問題,Trace的結果以下:.net

EventClass TextData ObjectID Type Mode
Lock:Acquired   21575115 5 - OBJECT 6 - IS
Lock:Acquired 1:77 0 6 - PAGE 6 - IS
Lock:Acquired [PLANGUIDE] 0 2 - DATABASE 3 - S
Lock:Acquired   21575115 5 - OBJECT 6 - IS
Lock:Acquired 1:77 0 6 - PAGE 6 - IS
Lock:Acquired 1:80 0 6 - PAGE 6 - IS
Lock:Acquired 1:89 0 6 - PAGE 6 - IS

Trace的前面兩行是鏈接2的Trace結果,從結果看,鏈接2僅使用了意向共享鎖(IS),並且只是表級和頁級,按照鎖的兼容性原則,IS和IX(鏈接1在表級和頁級僅使用了IX鎖)是不衝突的,因此鏈接2的查詢不會被阻塞。在增長了查詢的數據量後,Trace結果代表查仍是隻在表級和頁級使用了IS鎖(Trace結果的最後4行)。code

 

對於這個問題,解決的辦法固然就是提高鏈接1鎖的粒度,使用PAGLOCK表提示將鎖的粒度提高到頁級,這樣IS與X是衝突的,就能夠成功阻塞鏈接2。對象

但疑問就是,爲何查詢只在表級和頁級下意向共享鎖(IS),而不在行級下共享鎖(S),這個彷佛與聯機幫助上的說明不同(仍是一直以來理解上的誤差呢)。blog

 

這是由於SQL Server優化器故意爲之, 認爲在鏈接1上的XLOCK並無更改數據,因此沒有在鏈接2的行級加共享鎖(S),避免鏈接2被阻塞:

參考連接33樓回覆:https://bbs.csdn.net/topics/310262977

 

附:聯機幫助上關於鎖模式的說明

共享鎖
共享鎖(S 鎖)容許併發事務在封閉式併發控制下讀取 (SELECT) 資源。

 

更新鎖

更新鎖(U 鎖)能夠防止常見的死鎖。 在可重複讀或可序列化事務中,此事務讀取數據 [獲取資源(頁或行)的共享鎖(S 鎖)],而後修改數據 [此操做要求鎖轉換爲排他鎖(X 鎖)]。 若是兩個事務得到了資源上的共享模式鎖,而後試圖同時更新數據,則一個事務嘗試將鎖轉換爲排他鎖(X 鎖)。 共享模式到排他鎖的轉換必須等待一段時間,由於一個事務的排他鎖與其餘事務的共享模式鎖不兼容;發生鎖等待。 第二個事務試圖獲取排他鎖(X 鎖)以進行更新。 因爲兩個事務都要轉換爲排他鎖(X 鎖),而且每一個事務都等待另外一個事務釋放共享模式鎖,所以發生死鎖。

若要避免這種潛在的死鎖問題,請使用更新鎖(U 鎖)。 一次只有一個事務能夠得到資源的更新鎖(U 鎖)。 若是事務修改資源,則更新鎖(U 鎖)轉換爲排他鎖(X 鎖)。

 

排他鎖

排他鎖(X 鎖)能夠防止併發事務對資源進行訪問。 使用排他鎖(X 鎖)時,任何其餘事務都沒法修改數據;僅在使用 NOLOCK 提示或未提交讀隔離級別時纔會進行讀取操做。

數據修改語句(如 INSERT、UPDATE 和 DELETE)合併了修改和讀取操做。 語句在執行所需的修改操做以前首先執行讀取操做以獲取數據。 所以,數據修改語句一般請求共享鎖和排他鎖。 例如,UPDATE 語句可能根據與一個表的聯接修改另外一個表中的行。 在此狀況下,除了請求更新行上的排他鎖以外,UPDATE 語句還將請求在聯接表中讀取的行上的共享鎖。

 

意向鎖

數據庫引擎使用意向鎖來保護共享鎖(S 鎖)或排他鎖(X 鎖)放置在鎖層次結構的底層資源上。 意向鎖之因此命名爲意向鎖,是由於在較低級別鎖前可獲取它們,所以會通知意向將鎖放置在較低級別上。

 

 

原文連接

相關文章
相關標籤/搜索