2011-09-02 44 views
13

我會盡量詳細解釋我的問題,我將不勝感激任何幫助/建議。我的問題是關於由兩個查詢造成的死鎖(一個插入和一個更新)。我使用的MS-SQL服務器2008更新和插入查詢創建死鎖

我必須使用同一個數據庫的兩個應用:

  1. 網絡應用程序(在每一次請求多個記錄通過調用存儲過程插入到表印象)
  2. Windows服務(計算在一分鐘內,每分鐘進行,前一分鐘的所有展示,並設置一個標誌,每個通過一個存儲過程計算爲好印象的)

的Web應用程序中插入的印象記錄無使用交易,而Windows服務應用程序在使用IsolationLevel.ReadUncommitted交易時計算印象數。在Windows服務應用程序的存儲過程確實是這樣的:

Windows服務的存儲過程:

循環低谷所有具有isCalculated標誌設置爲false和日期< @now的印象,計數器加一以及另一個表格中與展示表格相關的其他數據,並將isCalculated標誌設置爲具有日期< @現在的展示次數爲true。由於此存儲過程是相當大的,在粘貼沒有意義,這裏是的PROC做什麼縮短的代碼片段:

DECLARE @nowTime datetime = convert(datetime, @now, 21) 
DECLARE dailyCursor CURSOR FOR 

SELECT Daily.dailyId, 
     Daily.spentDaily, 
     Daily.impressionsCountCache , 
     SUM(Impressions.amountCharged) as sumCharged, 
     COUNT(Impressions.impressionId) as countImpressions 
FROM Daily INNER JOIN Impressions on Impressions.dailyId = Daily.dailyId 
WHERE Impressions.isCharged=0 AND Impressions.showTime < @nowTime AND Daily.isActive = 1 
GROUP BY Daily.dailyId, Daily.spentDaily, Daily.impressionsCountCache 

OPEN dailyCursor 

DECLARE @dailyId int, 
     @spentDaily decimal(18,6), 
     @impressionsCountCache int, 
     @sumCharged decimal(18,6), 
     @countImpressions int 

FETCH NEXT FROM dailyCursor INTO @dailyId,@spentDaily, @impressionsCountCache, @sumCharged, @countImpressions 

WHILE @@FETCH_STATUS = 0 
    BEGIN 

     UPDATE Daily 
     SET spentDaily= @spentDaily + @sumCharged, 
      impressionsCountCache = @impressionsCountCache + @countImpressions 
     WHERE dailyId = @dailyId 

     FETCH NEXT FROM dailyCursor INTO @dailyId,@spentDaily, @impressionsCountCache, @sumCharged, @countImpressions 
    END 
CLOSE dailyCursor 
DEALLOCATE dailyCursor 

UPDATE Impressions 
SET isCharged=1 
WHERE showTime < @nowTime AND isCharged=0 

Web應用程序的存儲過程:

這個過程是非常簡單,它只是在表格中插入記錄。這裏是一個縮短的代碼片段:

INSERT INTO Impressions 
(dailyId, date, pageUrl,isCalculated) VALUES 
(@dailyId, @date, @pageUrl, 0) 

守則

調用這些存儲過程的代碼非常簡單,它只是創建的SQL命令傳遞所需的參數,並執行它們

//i send the date like this 
string date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff", 
CultureInfo.InvariantCulture); 

SqlCommand comm = sql.StoredProcedureCommand("storedProcName", 
parameters, values); 

我經常遇到死鎖(在web應用程序中發生異常,而不是windows服務),並且在使用SQL-Profiler之後,我發現死鎖可能是由於se兩個查詢(我沒有太多分析探查器數據的經驗)。

從SQL Server事件探查器收集到的最新跟蹤數據可以在這個問題上

理論上這兩個存儲過程應該能夠共同努力的底部找到,因爲第一個插入記錄的一個個date = DateTime.Now,第二個計算日期爲< DateTime.Now的印象數。

編輯:

這是在Windows服務應用程序代碼的運行:

SQL sql = new SQL(); 
DateTime endTime = DateTime.Now; 
//our custom DAL class that opens a connection 
sql.StartTransaction(IsolationLevel.ReadUncommitted); 
try 
{ 
    List<string> properties = new List<string>() { "now" }; 
    List<string> values = new List<string>() { endTime.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture) }; 
    SqlCommand comm = sql.StoredProcedureCommannd("ChargeImpressions", properties, values); 
    comm.Transaction = sql.Transaction; 
    ok = sql.CheckExecute(comm); 
} 
catch (Exception up) 
{ 
    ok = false; 
    throw up; 
} 
finally 
{ 
    if (ok) 
     sql.CommitTransaction(); 
    else 
     sql.RollbackTransactions(); 
    CloseConn(); 
} 

編輯:

我加在兩個表的索引由馬丁·史密斯這樣的建議:

CREATE NONCLUSTERED INDEX [IDX_Daily_DailyId] ON [dbo].[Daily] 
(
    [daily] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
GO 

and

CREATE NONCLUSTERED INDEX [IDX_Impressions_isCharged_showTime] ON [dbo].[Impressions] 
(
    [isCharged] ASC, 
    [showTime] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
GO 

目前沒有異常,將報告後

編輯:

不幸的是這並沒有解決死鎖問題。我將在分析器中啓動死鎖跟蹤,以查看死鎖是否與以前相同。

編輯:

粘貼新的跟蹤(對我來說,它看起來一樣的前一個),也沒有捕捉到執行計劃的屏幕(其過大),但here is the xml from the execution plan。而這裏是截圖插入查詢的執行計劃:

execution plan of the insert query

<deadlock victim="process14e29e748"> 
    <process-list> 
    <process id="process14e29e748" taskpriority="0" logused="952" waitresource="KEY: 6:72057594045071360 (f473d6a70892)" waittime="4549" ownerId="2507482845" transactionname="INSERT" lasttranstarted="2011-09-05T11:59:16.587" XDES="0x15bef83b0" lockMode="S" schedulerid="1" kpid="2116" status="suspended" spid="65" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2011-09-05T11:59:16.587" lastbatchcompleted="2011-09-05T11:59:16.587" clientapp=".Net SqlClient Data Provider" hostpid="2200" isolationlevel="snapshot (5)" xactid="2507482845" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056"> 
    <executionStack> 
    <frame procname="dbo.InsertImpression" line="27" stmtstart="2002" stmtend="2560" sqlhandle="0x03000600550e30512609e200529f00000100000000000000"> 
INSERT INTO Impressions 
    (dailyId, languageId, showTime, pageUrl, amountCharged, age, ipAddress, userAgent, portalId, isCharged,isCalculated) VALUES 
    (@dailyId, @languageId, @showTime, @pageUrl, @amountCharged, @age, @ip, @userAgent, @portalId, 0, 0)  </frame> 
    </executionStack> 
    <inputbuf> 
Proc [Database Id = 6 Object Id = 1362103893] </inputbuf> 
    </process> 
    <process id="process6c9dc8" taskpriority="0" logused="335684" waitresource="KEY: 6:72057594045464576 (5fcc21780b69)" waittime="4475" ownerId="2507482712" transactionname="transaction_name" lasttranstarted="2011-09-05T11:59:15.737" XDES="0x1772119b0" lockMode="U" schedulerid="2" kpid="3364" status="suspended" spid="88" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2011-09-05T11:59:15.737" lastbatchcompleted="2011-09-05T11:59:15.737" clientapp=".Net SqlClient Data Provider" hostpid="1436" isolationlevel="read uncommitted (1)" xactid="2507482712" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056"> 
    <executionStack> 
    <frame procname="dbo.ChargeImpressions" line="60" stmtstart="4906" stmtend="5178" sqlhandle="0x03000600e3c5474f0609e200529f00000100000000000000"> 
UPDATE Impressions 
    SET isCharged=1 
    WHERE showTime &amp;lt; @nowTime AND isCharged=0 

    </frame> 
    </executionStack> 
    <inputbuf> 
Proc [Database Id = 6 Object Id = 1330103779] </inputbuf> 
    </process> 
    </process-list> 
    <resource-list> 
    <keylock hobtid="72057594045071360" dbid="6" objectname="dbo.Daily" indexname="PK_Daily" id="lock14c6aab00" mode="X" associatedObjectId="72057594045071360"> 
    <owner-list> 
    <owner id="process6c9dc8" mode="X"/> 
    </owner-list> 
    <waiter-list> 
    <waiter id="process14e29e748" mode="S" requestType="wait"/> 
    </waiter-list> 
    </keylock> 
    <keylock hobtid="72057594045464576" dbid="6" objectname="dbo.Impressions" indexname="IDX_Impressions_isCharged_showTime" id="lock14c901200" mode="X" associatedObjectId="72057594045464576"> 
    <owner-list> 
    <owner id="process14e29e748" mode="X"/> 
    </owner-list> 
    <waiter-list> 
    <waiter id="process6c9dc8" mode="U" requestType="wait"/> 
    </waiter-list> 
    </keylock> 
    </resource-list> 
</deadlock> 

編輯:

之後喬恩建議ATHAN迪金森:

  1. 我改變我添加了存儲過程(除去光標),
  2. 我改變了IDX_Impressions_isCharged_showTime不允許PAGE_LOCKS和
  3. -1秒到Windows服務應用程序的屬性@now避免臨界死鎖的情況。

更新:

查詢執行時間的最後更改後下降,但例外的數量也沒有。

希望最後更新:

由馬丁·史密斯提出的變化是現在生活,插入查詢現在使用的非聚集索引,並在理論上這應該解決這個問題。目前沒有例外報告(保持我的手指交叉)

+1

本文介紹了未提交讀和鎖定行爲:http://beyondrelational.com/blogs/jacob/archive/2008/08/28/sql -server-transaction-isolation-level-read-committed.aspx –

+1

由於FK與'dbo.Daily'的關係,這個問題似乎已經出現,你對這兩個表有什麼索引? –

+0

我在這兩個表中都有幾個索引。每日有兩個索引(主要包含「dailyId」和非集羣非唯一「日期」)。展示次數有3個索引(主集羣「impressionId」,非集羣非唯一「impressionId,dailyId,isCalculated,isCharged」和非集羣非唯一「isCharged,portalId」) – Atzoya

回答

2

您的Windows服務遊標更新Daily中的各個行,爲此需要使用X鎖。這些不會在交易結束前發佈。然後

你的web應用程序做一個INSERT INTO Impressions,並保持對新插入的行的X鎖,而等待在DailyS鎖行的一個由其它進程鎖定。它需要讀取這個來驗證FK約束。

然後,您的Windows服務在Impressions上執行更新,並在其掃描的行上執行U鎖。沒有索引允許它尋找行,所以這個掃描包括由web應用程序添加的行。

所以

(1)您可以在showTime, isCharged反之亦然(檢查執行計劃)加綜合指數Impressions允許通過索引被發現,Windows服務將更新的行尋求相當比全面掃描。

-Or

(2)你可以添加在Daily(DailyId)聚集索引冗餘非。這將比聚簇更窄,所以FK驗證可能會優先使用聚簇索引行上的S鎖。

編輯

免責聲明:以下是基於假設和觀察,而不是什麼我已經找到了記錄!

看來,想法(2)不「按原樣」工作。執行計劃顯示,不管現在是否有更窄的索引,FK驗證仍會繼續針對聚簇索引進行。 sys.foreign_keys的列有referenced_object_id, key_index_id,我推測驗證將總是發生在那裏列出的索引上,並且查詢優化器目前沒有考慮替代方案,但沒有找到任何記錄此內容的方法。

我發現,在sys.foreign_keys相關的值和查詢計劃更改爲開始使用較窄的指數後,我刪除並重新添加的外鍵約束。

CREATE TABLE Daily(
    DailyId INT IDENTITY(1,1) PRIMARY KEY CLUSTERED NOT NULL, 
    Filler CHAR(4000) NULL, 
) 

INSERT INTO Daily VALUES (''); 


CREATE TABLE Impressions(
    ImpressionId INT IDENTITY(1,1) PRIMARY KEY NOT NULL, 
    DailyId INT NOT NULL CONSTRAINT FK REFERENCES Daily (DailyId), 
    Filler CHAR(4000) NULL, 
) 

/*Execution Plan uses clustered index - There is no NCI*/ 
INSERT INTO Impressions VALUES (1,1) 

ALTER TABLE Daily ADD CONSTRAINT 
    UQ_Daily UNIQUE NONCLUSTERED(DailyId) 

/*Execution Plan still use clustered index even after NCI created*/  
INSERT INTO Impressions VALUES (1,1) 

ALTER TABLE Impressions DROP CONSTRAINT FK 
ALTER TABLE Impressions WITH CHECK ADD CONSTRAINT FK FOREIGN KEY(DailyId) 
REFERENCES Daily (DailyId)  

/*Now Execution Plan now uses non clustered index*/  
INSERT INTO Impressions VALUES (1,1)  

Plan

+1

謝謝您提出的解決方案,您的幫助和時間。我現在創建了索引並且沒有例外。如果我沒有收到任何例外,我會接受你的回答。 – Atzoya

+0

不幸的是增加這些指標並沒有解決這個問題(200個左右拋出異常整個週末):(我會跑,看看是否有死鎖 – Atzoya

+0

@Atzoya任何改動痕跡 - 你能發佈最近僵局的一個?XML轉換的問題也將是很好看的違規語句的執行計劃 –

4

避免遊標,該查詢根本不需要他們。 SQL是而不是命令式語言(這就是爲什麼它得到一個壞名字,因爲每個人都使用它作爲一個) - 它是一種集合語言。

你可以做的第一件事是加快你的SQL的基本執行,更少的時間分析/執行查詢意味着減少死鎖的機會:

  • 前綴所有與[dbo]表 - 這削減高達解析階段30%的折扣。
  • 別名您的表格 - 它在計劃階段切斷一小部分。
  • 引用標識符可能加快速度。
  • 這些是來自前SQL-PM的提示,在任何人決定提出異議之前。

可以使用CTE來獲取數據更新,然後使用UPDATE ... FROM ... SELECT語句來完成實際的更新。這將比光標快,因爲光標是狗慢相比干淨設置操作(即使是最快的'消防軟管'光標像你的)。花費更少的時間意味着更少的死鎖機會。 注意:我沒有您的原始表格,我無法驗證這一點 - 所以請根據開發數據庫進行檢查。

DECLARE @nowTime datetime = convert(datetime, @now, 21); 

WITH [DailyAggregates] AS 
(
    SELECT 
     [D].[dailyId] AS [dailyId], 
     [D].[spentDaily] AS [spentDaily], 
     [D].[impressionsCountCache] AS [impressionsCountCache], 
     SUM([I].[amountCharged]) as [sumCharged], 
     COUNT([I].[impressionId]) as [countImpressions] 
     FROM [dbo].[Daily] AS [D] 
      INNER JOIN [dbo].[Impressions] AS [I] 
       ON [I].[dailyId] = [D].[dailyId] 
     WHERE [I].[isCharged] = 0 
      AND [I].[showTime] < @nowTime 
      AND [D].[isActive] = 1 
    GROUP BY [D].[dailyId], [D].[spentDaily], [D].[impressionsCountCache] 
) 
UPDATE [dbo].[Daily] 
    SET [spentDaily] = [A].[spentDaily] + [A].[sumCharged], 
     [impressionsCountCache] = [A].[impressonsCountCache] + [A].[countImpressions] 
    FROM [Daily] AS [D] 
    INNER JOIN [DailyAggregates] AS [A] 
     ON [D].[dailyId] = [A].[dailyId]; 

UPDATE [dbo].[Impressions] 
SET [isCharged] = 1 
WHERE [showTime] < @nowTime 
    AND [isCharged] = 0; 

而且你可以在你的索引禁止頁鎖,這將減少鎖定一整頁幾排的,因爲鎖定升級的機會(僅排的一定比例需要將整個前鎖定頁面被鎖定)。

CREATE NONCLUSTERED INDEX [IDX_Impressions_isCharged_showTime] ON [dbo].[Impressions]    
(
    [showTime] ASC, -- I have a hunch that switching these around might have an effect. 
    [isCharged] ASC 
) 
WITH (ALLOW_PAGE_LOCKS = OFF) 
ON [PRIMARY] 
GO 

這隻會減輕死鎖的可能性。您可以嘗試限制@now過去的日期(即today - 1 day)以確保插入的行不落入更新謂詞;很可能會完全防止僵局。

+0

+1我的下一步將是看整個過程,但看起來你擊敗了我! –

+0

我現在會試試看,但我真的不知道這會有多大改進。印象表格不斷有大約40萬條記錄,或多或少,我每天遇到20個例外。我也會嘗試並退回DateTime.Now 1秒,以防萬一 – Atzoya

+0

@Atzoya,你從服務器獲取日期的任何原因?爲什麼不在SQL中使用'GETDATE()'?如果你的服務器的時鐘不同步,這可能會導致問題。 –

0

我確定需要修改其他答案,因爲例如在您的情況下不需要使用遊標......從您提供的代碼中,甚至不需要WHILE .. 。

我沒有SQL服務器的傢伙......如果我需要做什麼你的存儲過程是做我將確保@nowTime = DateTime.Now.AddSeconds(-1)和代碼它類似於以下:

BEGIN 

UPDATE Daily D SET 
D.spentDaily= D.spentDaily + (SELECT SUM(I.amountCharged) FROM Impressions I WHERE I.isCharged=0 AND I.showTime < @nowTime AND I.DailyId = D.DailyId), 
D.impressionsCountCache = D.impressionsCountCache + (SELECT COUNT(I.impressionId) FROM Impressions I WHERE I.isCharged=0 AND I.showTime < @nowTime AND I.DailyId = D.DailyId) 
WHERE D.DailyId IN (SELECT I.DailyId FROM Impressions I WHERE I.isCharged=0 AND I.showTime < @nowTime AND I.DailyId = D.DailyId) AND D.isActive = 1; 

UPDATE Impressions I SET 
I.isCharged=1 
WHERE I.showTime < @nowTime AND I.isCharged=0; 

COMMIT; 

END 

即使在高負載從來沒有任何並行的死鎖問題INSERT/在Impressions這樣/DELETE(雖然這是甲骨文)... HTH