數據庫堵塞和死鎖詳解


數據庫阻塞:第一個連接占有資源沒有釋放,而第二個連接需要獲取這個資源。如果第一個連接沒有提交或者回滾,第二個連接會一直等待下去,直到第一個連接釋放該資源為止。對於阻塞,數據庫無法處理,所以對數據庫操作要及時地提交或者回滾。

阻塞示例:

第一個鏈接執行:

復制代碼
--創建表
CREATE TABLE [dbo].[A_Table](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [NAME] [Nvarchar](50) NOT NULL ,
    [CreateTime] [datetime] NOT NULL)

--插入數據
INSERT INTO [dbo].[A_Table]
           ([NAME],[CreateTime])
     VALUES
           ('joye1',GETDATE())
INSERT INTO [dbo].[A_Table]
           ([NAME],[CreateTime])
     VALUES
           ('joye2',GETDATE())
INSERT INTO [dbo].[A_Table]
           ([NAME],[CreateTime])
     VALUES
           ('joye3',GETDATE())

select * from [dbo].[A_Table]

begin tran
update [dbo].[A_Table] set [NAME]='joye' where ID=2
--commit tran第一個連接,不提交或者回滾:
復制代碼

第二個鏈接執行:

1
update  [dbo].[A_Table]  set  [ NAME ]= 'joye1'  where  ID=2

 第二個一直在執行中,因為第一個連接占有A_Table表沒有釋放資源,而第二個連接一直在等待第一個連接釋放該資源。默認情況下除非設置了LOCK_TIMEOUT,否則事務會一直等待下去。

避免阻塞的最好方式就是及時的提交事務和回滾事務。

查詢阻塞的sql語句:

復制代碼
--sql查詢阻塞的sql語句
SELECT SPID=p.spid,
       DBName = convert(CHAR(20),d.name),
       ProgramName = program_name,
       LoginName = convert(CHAR(20),l.name),
       HostName = convert(CHAR(20),hostname),
       Status = p.status,
       BlockedBy = p.blocked,
       LoginTime = login_time,
       QUERY = CAST(TEXT AS VARCHAR(MAX))
FROM   MASTER.dbo.sysprocesses p
       INNER JOIN MASTER.dbo.sysdatabases d
         ON p.dbid = d.dbid
       INNER JOIN MASTER.dbo.syslogins l
         ON p.sid = l.sid
       CROSS APPLY sys.dm_exec_sql_text(sql_handle)
WHERE  p.blocked = 0
       AND EXISTS (SELECT 1
                   FROM   MASTER..sysprocesses p1
                   WHERE  p1.blocked = p.spid)

--查詢引起阻塞和被阻塞的sql
SELECT wt.blocking_session_id AS BlockingSessesionId
      ,sp.program_name AS Blocking_ProgramName
      ,COALESCE(sp.LOGINAME, sp.nt_username)  AS Blocking_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 WITH(NOLOCK)
   INNER JOIN sys.databases AS db  WITH(NOLOCK)
  ON db.database_id = tl.resource_database_id
INNER JOIN sys.dm_os_waiting_tasks AS wt  WITH(NOLOCK)
  ON tl.lock_owner_address = wt.resource_address
INNER JOIN sys.dm_exec_connections  ec1 WITH(NOLOCK)
  ON ec1.session_id = tl.request_session_id
INNER JOIN sys.dm_exec_connections  ec2 WITH(NOLOCK)
  ON ec2.session_id = wt.blocking_session_id
LEFT OUTER JOIN master.dbo.sysprocesses AS sp WITH(NOLOCK)
  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 
復制代碼

 

數據庫死鎖:第一個連接占有資源沒有釋放,准備獲取第二個連接所占用的資源,而第二個連接占有資源沒有釋放, 准備獲取第一個連接所占用的資源。這種互相占有對方需要獲取的資源的現象叫做死鎖。對於死鎖,數據庫處理方法:犧牲一個。

死鎖實例:

創建B表和C表

復制代碼
--創建B表
CREATE TABLE [dbo].[B_Table](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [NAME] [Nvarchar](50) NOT NULL ,
    [CreateTime] [datetime] NOT NULL)

--插入數據
INSERT INTO [dbo].[B_Table]
           ([NAME],[CreateTime])
     VALUES
           ('joye1',GETDATE())
INSERT INTO [dbo].[B_Table]
           ([NAME],[CreateTime])
     VALUES
           ('joye2',GETDATE())
INSERT INTO [dbo].[B_Table]
           ([NAME],[CreateTime])
     VALUES
           ('joye3',GETDATE())

select * from [dbo].[B_Table]

begin tran
update [dbo].[B_Table] set [NAME]='joye' where ID=2
waitfor  delay '0:0:20'
update [dbo].[C_Table] set [NAME]='joye' where ID=2
--commit tran
復制代碼
復制代碼
--創建C表
CREATE TABLE [dbo].[C_Table](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [NAME] [Nvarchar](50) NOT NULL ,
    [CreateTime] [datetime] NOT NULL)

--插入數據
INSERT INTO [dbo].[C_Table]
           ([NAME],[CreateTime])
     VALUES
           ('joye1',GETDATE())
INSERT INTO [dbo].[C_Table]
           ([NAME],[CreateTime])
     VALUES
           ('joye2',GETDATE())
INSERT INTO [dbo].[C_Table]
           ([NAME],[CreateTime])
     VALUES
           ('joye3',GETDATE())

select * from [dbo].[C_Table]

begin tran
update [dbo].[C_Table] set [NAME]='joye' where ID=2
waitfor  delay '0:0:20'
update [dbo].[B_Table] set [NAME]='joye' where ID=2
--commit tran
復制代碼

執行結果:

一個事務執行成功,如圖

 

下個事務被犧牲:

消息 1205,級別 13,狀態 45,第 26 行
事務(進程 ID 58)與另一個進程被死鎖在 鎖 資源上,並且已被選作死鎖犧牲品。請重新運行該事務。

因為第一個連接占有表B,想要獲取表C的資源,而第二個連接占有表C,想要獲取表B的資源,這種互相占有對方想要獲取的資源,滿足死鎖現象。最后第一個連接執行成功,而第二個連接事務被選作死鎖犧牲品。兩個或多個進程之間的相互等待。但是由於SQL Server有數據庫引擎死鎖檢測方案,至少5秒鍾會消除一個現有的死鎖。對性能的影響往往沒有阻塞嚴重。

避免死鎖:

盡管死鎖不能完全避免,但是可以把機會降到最低:

按同一順序訪問對象(如果所有並發事務按同一順序訪問對象,則發生死鎖的可能性會降低。)

避免事務中的用戶交互(避免編寫包含用戶交互的事務,因為沒有用戶干預的批處理的運行速度遠快於必須等待用戶響應時的查詢速度。

保持事務簡短並處於一個批處理中(運行時間越長,等待時間就越長,造成死鎖的機會就越高。)

使用較低的隔離級別(確定事務能否在低隔離級別上運行。盡可能使用較低的隔離級別。)

調整語句的執行計划,減少鎖的申請數目(可以從執行計划中找出哪些資源耗得比較多。此時鎖的數目也會相應增多)

查詢死鎖:

復制代碼
-- 查詢死鎖
select request_session_id spid,OBJECT_NAME(resource_associated_entity_id) tableName    
from sys.dm_tran_locks   
where resource_type='OBJECT' 

--查詢主機名,數據庫等信息
exec sp_who2 59

--殺死死鎖進程
kill 59
復制代碼

查詢如下:

 也可使用SQL Server Profile 跟蹤阻塞或者死鎖的SQL

https://www.cnblogs.com/yinrq/p/12742779.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM