sql数据库死锁
1. sql死锁怎么解决,帮帮忙!
这个以前是在网上看到的你可以试试
运行时错误:-2147217900(80040e14)
SqlDumpExceptionHandler:进程53发生严重的异常c0000005 EXCEPTION_ACCESS_VIOLATION.SQL Server将终止进程
运行时错误 -2147467259(80004005)
事务(进程ID 63)与另一个进程已被锁在 LOCK 资源上,且该事务已被选作死锁牺牲品。请重新运行该事物
1. 用下面的语句检查表是否有问题, 如果有, 按检查的结果提示修复
use 你的库名
dbcc checktable('vw_T_Dept')
2. 重建索引
dbcc dbreindex('vw_T_Dept')
2. 如何处理SQL Server死锁问题
死锁,简而言之,两个或者多个trans,同时请求对方正在请求的某个对象,导致双方互相等待。简单的例子如下:
trans1 trans2
------------------------------------------------------------------------
1.IDBConnection.BeginTransaction 1.IDBConnection.BeginTransaction
2.update table A 2.update table B
3.update table B 3.update table A
4.IDBConnection.Commit 4.IDBConnection.Commit
那么,很容易看到,如果trans1和trans2,分别到达了step3,那么trans1会请求对于B的X锁,trans2会请求对于A的X锁,而二者的锁在step2上已经被对方分别持有了。由于得不到锁,后面的Commit无法执行,这样双方开始死锁。
好,我们看一个简单的例子,来解释一下,应该如何解决死锁问题。
-- Batch #1
CREATE DATABASE deadlocktest
GO
USE deadlocktest
SET NOCOUNT ON
DBCC TRACEON (1222, -1)
-- 在SQL2005中,增加了一个新的dbcc参数,就是1222,原来在2000下,我们知道,可以执行dbcc
--traceon(1204,3605,-1)看到所有的死锁信息。SqlServer 2005中,对于1204进行了增强,这就是1222。
GO
IF OBJECT_ID ('t1') IS NOT NULL DROP TABLE t1
IF OBJECT_ID ('p1') IS NOT NULL DROP PROC p1
IF OBJECT_ID ('p2') IS NOT NULL DROP PROC p2
GO
CREATE TABLE t1 (c1 int, c2 int, c3 int, c4 char(5000))
GO
DECLARE @x int
SET @x = 1
WHILE (@x <= 1000) BEGIN
INSERT INTO t1 VALUES (@x*2, @x*2, @x*2, @x*2)
SET @x = @x + 1
END
GO
CREATE CLUSTERED INDEX cidx ON t1 (c1)
CREATE NONCLUSTERED INDEX idx1 ON t1 (c2)
GO
CREATE PROC p1 @p1 int AS SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
GO
CREATE PROC p2 @p1 int AS
UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
UPDATE t1 SET c2 = c2-1 WHERE c1 = @p1
GO
上述sql创建一个deadlock的示范数据库,插入了1000条数据,并在表t1上建立了c1列的聚集索引,和c2列的非聚集索引。另外创建了两个sp,分别是从t1中select数据和update数据。
好,打开一个新的查询窗口,我们开始执行下面的query:
-- Batch #2
USE deadlocktest
SET NOCOUNT ON
WHILE (1=1) EXEC p2 4
GO
开始执行后,然后我们打开第三个查询窗口,执行下面的query:
-- Batch #3
USE deadlocktest
SET NOCOUNT ON
CREATE TABLE #t1 (c2 int, c3 int)
GO
WHILE (1=1) BEGIN
INSERT INTO #t1 EXEC p1 4
TRUNCATE TABLE #t1
END
GO
开始执行,哈哈,很快,我们看到了这样的错误信息:
Msg 1205, Level 13, State 51, Procere p1, Line 4
Transaction (Process ID 54) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
spid54发现了死锁。
那么,我们该如何解决它?
在SqlServer 2005中,我们可以这么做:
1.在trans3的窗口中,选择EXEC p1 4,然后right click,看到了菜单了吗?选择Analyse Query in Database Engine Tuning Advisor。
2.注意右面的窗口中,wordload有三个选择:负载文件、表、查询语句,因为我们选择了查询语句的方式,所以就不需要修改这个radio option了。
3.点左上角的Start Analysis按钮
4.抽根烟,回来后看结果吧!出现了一个分析结果窗口,其中,在Index Recommendations中,我们发现了一条信息:大意是,在表t1上增加一个非聚集索引索引:t2+t1。
5.在当前窗口的上方菜单上,选择Action菜单,选择Apply Recommendations,系统会自动创建这个索引。
重新运行batch #3,呵呵,死锁没有了。
这种方式,我们可以解决大部分的Sql Server死锁问题。那么,发生这个死锁的根本原因是什么呢?为什么增加一个non clustered index,问题就解决了呢? 这次,我们分析一下,为什么会死锁呢?再回顾一下两个sp的写法:
CREATE PROC p1 @p1 int AS
SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
GO
CREATE PROC p2 @p1 int AS
UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
UPDATE t1 SET c2 = c2-1 WHERE c1 = @p1
GO
很奇怪吧!p1没有insert,没有delete,没有update,只是一个select,p2才是update。这个和我们前面说过的,trans1里面updata A,update B;trans2里面upate B,update A,根本不贴边啊!
那么,什么导致了死锁?
需要从事件日志中,看sql的死锁信息:
Spid X is running this query (line 2 of proc [p1], inputbuffer “… EXEC p1 4 …”):
SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
Spid Y is running this query (line 2 of proc [p2], inputbuffer “EXEC p2 4”):
UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
The SELECT is waiting for a Shared KEY lock on index t1.cidx. The UPDATE holds a conflicting X lock.
The UPDATE is waiting for an eXclusive KEY lock on index t1.idx1. The SELECT holds a conflicting S lock.
首先,我们看看p1的执行计划。怎么看呢?可以执行set statistics profile on,这句就可以了。下面是p1的执行计划
SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
|--Nested Loops(Inner Join, OUTER REFERENCES:([Uniq1002], [t1].[c1]))
|--Index Seek(OBJECT:([t1].[idx1]), SEEK:([t1].[c2] >= [@p1] AND [t1].[c2] <= [@p1]+(1)) ORDERED FORWARD)
|--Clustered Index Seek(OBJECT:([t1].[cidx]), SEEK:([t1].[c1]=[t1].[c1] AND [Uniq1002]=[Uniq1002]) LOOKUP ORDERED FORWARD)
我们看到了一个nested loops,第一行,利用索引t1.c2来进行seek,seek出来的那个rowid,在第二行中,用来通过聚集索引来查找整行的数据。这是什么?就是bookmark lookup啊!为什么?因为我们需要的c2、c3不能完全的被索引t1.c1带出来,所以需要书签查找。
好,我们接着看p2的执行计划。
UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
|--Clustered Index Update(OBJECT:([t1].[cidx]), OBJECT:([t1].[idx1]), SET:([t1].[c2] = [Expr1004]))
|--Compute Scalar(DEFINE:([Expr1013]=[Expr1013]))
|--Compute Scalar(DEFINE:([Expr1004]=[t1].[c2]+(1), [Expr1013]=CASE WHEN CASE WHEN ...
|--Top(ROWCOUNT est 0)
|--Clustered Index Seek(OBJECT:([t1].[cidx]), SEEK:([t1].[c1]=[@p1]) ORDERED FORWARD)
通过聚集索引的seek找到了一行,然后开始更新。这里注意的是,update的时候,它会申请一个针对clustered index的X锁的。
实际上到这里,我们就明白了为什么update会对select产生死锁。update的时候,会申请一个针对clustered index的X锁,这样就阻塞住了(注意,不是死锁!)select里面最后的那个clustered index seek。死锁的另一半在哪里呢?注意我们的select语句,c2存在于索引idx1中,c1是一个聚集索引cidx。问题就在这里!我们在p2中更新了c2这个值,所以sqlserver会自动更新包含c2列的非聚集索引:idx1。而idx1在哪里?就在我们刚才的select语句中。而对这个索引列的更改,意味着索引集合的某个行或者某些行,需要重新排列,而重新排列,需要一个X锁。
SO………,问题就这样被发现了。
总结一下,就是说,某个query使用非聚集索引来select数据,那么它会在非聚集索引上持有一个S锁。当有一些select的列不在该索引上,它需要根据rowid找到对应的聚集索引的那行,然后找到其他数据。而此时,第二个的查询中,update正在聚集索引上忙乎:定位、加锁、修改等。但因为正在修改的某个列,是另外一个非聚集索引的某个列,所以此时,它需要同时更改那个非聚集索引的信息,这就需要在那个非聚集索引上,加第二个X锁。select开始等待update的X锁,update开始等待select的S锁,死锁,就这样发生鸟。
那么,为什么我们增加了一个非聚集索引,死锁就消失鸟?我们看一下,按照上文中自动增加的索引之后的执行计划:
SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
|--Index Seek(OBJECT:([deadlocktest].[dbo].[t1].[_dta_index_t1_7_2073058421__K2_K1_3]), SEEK:([deadlocktest].[dbo].[t1].[c2] >= [@p1] AND [deadlocktest].[dbo].[t1].[c2] <= [@p1]+(1)) ORDERED FORWARD)
哦,对于clustered index的需求没有了,因为增加的覆盖索引已经足够把所有的信息都select出来。就这么简单。
实际上,在sqlserver 2005中,如果用profiler来抓eventid:1222,那么会出现一个死锁的图,很直观的说。
下面的方法,有助于将死锁减至最少(详细情况,请看SQLServer联机帮助,搜索:将死锁减至最少即可。
按同一顺序访问对象。
避免事务中的用户交互。
保持事务简短并处于一个批处理中。
使用较低的隔离级别。
使用基于行版本控制的隔离级别。
将 READ_COMMITTED_SNAPSHOT 数据库选项设置为 ON,使得已提交读事务使用行版本控制。
使用快照隔离。
使用绑定连接。
3. 数据库中死锁是什么产生的
数据库操作的死锁是不可避免的,本文并不打算讨论死锁如何产生,重点在于解决死锁,通过SQL Server 2005, 现在似乎有了一种新的解决办法。
将下面的SQL语句放在两个不同的连接里面,并且在5秒内同时执行,将会发生死锁。
use Northwind
begin tran
insert into Orders(CustomerId) values(@#ALFKI@#)
waitfor delay @#00:00:05@#
select * from Orders where CustomerId = @#ALFKI@#
commit
print @#end tran@#
SQL Server对付死锁的办法是牺牲掉其中的一个,抛出异常,并且回滚事务。在SQL Server 2000,语句一旦发生异常,T-SQL将不会继续运行,上面被牺牲的连接中, print @#end tran@#语句将不会被运行,所以我们很难在SQL Server 2000的T-SQL中对死锁进行进一步的处理。
现在不同了,SQL Server 2005可以在T-SQL中对异常进行捕获,这样就给我们提供了一条处理死锁的途径:
下面利用的try ... catch来解决死锁。
SET XACT_ABORT ON
declare @r int
set @r = 1
while @r <= 3
begin
begin tran
begin try
insert into Orders(CustomerId) values(@#ALFKI@#)
waitfor delay @#00:00:05@#
select * from Orders where CustomerId = @#ALFKI@#
commit
break
end try
begin catch
rollback
waitfor delay @#00:00:03@#
set @r = @r + 1
continue
end catch
end
解决方法当然就是重试,但捕获错误是前提。rollback后面的waitfor不可少,发生冲突后需要等待一段时间,@retry数目可以调整以应付不同的要求。
但是现在又面临一个新的问题: 错误被掩盖了,一但问题发生并且超过3次,异常却不会被抛出。SQL Server 2005 有一个RaiseError语句,可以抛出异常,但却不能直接抛出原来的异常,所以需要重新定义发生的错误,现在,解决方案变成了这样:
declare @r int
set @r = 1
while @r <= 3
begin
begin tran
begin try
insert into Orders(CustomerId) values(@#ALFKI@#)
waitfor delay @#00:00:05@#
select * from Orders where CustomerId = @#ALFKI@#
commit
break
end try
begin catch
rollback
waitfor delay @#00:00:03@#
set @r = @r + 1
continue
end catch
end
if ERROR_NUMBER() <> 0
begin
declare @ErrorMessage nvarchar(4000);
declare @ErrorSeverity int;
declare @ErrorState int;
select
@ErrorMessage = ERROR_MESSAGE(),
@ErrorSeverity = ERROR_SEVERITY(),
@ErrorState = ERROR_STATE();
raiserror (@ErrorMessage,
@ErrorSeverity,
@ErrorState
);
end
4. 用sql语句,怎么解决mysql数据库死锁
MySQL死锁问题的相关知识是本文我们主要要介绍的内容,接下来我们就来一一介绍这部分内容,希望能够对您有所帮助。
1、MySQL常用存储引擎的锁机制
MyISAM和MEMORY采用表级锁(table-level locking)
BDB采用页面锁(page-level locking)或表级锁,默认为页面锁
InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁
2、各种锁特点
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
3、各种锁的适用场景
表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用
行级锁则更适合于有大量按索引条件并发更新数据,同时又有并发查询的应用,如一些在线事务处理系统
4、死锁
是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
表级锁不会产生死锁。所以解决死锁主要还是针对于最常用的InnoDB。
5、死锁举例分析
在MySQL中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。
在UPDATE、DELETE操作时,MySQL不仅锁定WHERE条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的next-key locking。
例如,一个表db。tab_test,结构如下:
id:主键;
state:状态;
time:时间;
索引:idx_1(state,time)
出现死锁日志如下:
?***(1) TRANSACTION:
?TRANSACTION 0 677833455, ACTIVE 0 sec, process no 11393, OSthread id 278546 starting index read
?mysql tables in use 1, locked 1
?LOCK WAIT 3 lock struct(s), heap size 320
?MySQL thread id 83, query id 162348740 dcnet03 dcnet Searching rows for update
?update tab_test set state=1064,time=now() where state=1061 and time < date_sub(now(), INTERVAL 30 minute) (任务1的sql语句)
?***(1) WAITING FOR THIS LOCK TO BE GRANTED: (任务1等待的索引记录)
?RECORD LOCKS space id 0 page no 849384 n bits 208 index `PRIMARY` of table `db/tab_test` trx id 0 677833455 _mode X locks rec but not gap waiting
?Record lock, heap no 92 PHYSICAL RECORD: n_fields 11; compact format; info bits 0
?0: len 8; hex 800000000097629c; asc b ;; 1: len 6; hex 00002866eaee; asc (f ;; 2: len 7; hex 00000d40040110; asc @ ;; 3: len 8; hex 80000000000050b2; asc P ;; 4: len 8; hex 800000000000502a; asc P*;; 5: len 8; hex 8000000000005426; asc T&;; 6: len 8; hex 800012412c66d29c; asc A,f ;; 7: len 23; hex 8616e642e706870; asc xxx.com/;; 8: len 8; hex 800000000000042b; asc +;; 9: len 4; hex 474bfa2b; asc GK +;; 10: len 8; hex 8000000000004e24; asc N$;;
?*** (2) TRANSACTION:
?TRANSACTION 0 677833454, ACTIVE 0 sec, process no 11397, OS thread id 344086 updating or deleting, thread declared inside InnoDB 499
?mysql tables in use 1, locked 1
?3 lock struct(s), heap size 320, undo log entries 1
?MySQL thread id 84, query id 162348739 dcnet03 dcnet Updating update tab_test set state=1067,time=now () where id in (9921180) (任务2的sql语句)
?*** (2) HOLDS THE LOCK(S): (任务2已获得的锁)
?RECORD LOCKS space id 0 page no 849384 n bits 208 index `PRIMARY` of table `db/tab_test` trx id 0 677833454 lock_mode X locks rec but not gap
?Record lock, heap no 92 PHYSICAL RECORD: n_fields 11; compact format; info bits 0
?0: len 8; hex 800000000097629c; asc b ;; 1: len 6; hex 00002866eaee; asc (f ;; 2: len 7; hex 00000d40040110; asc @ ;; 3: len 8; hex 80000000000050b2; asc P ;; 4: len 8; hex 800000000000502a; asc P*;; 5: len 8; hex 8000000000005426; asc T&;; 6: len 8; hex 800012412c66d29c; asc A,f ;; 7: len 23; hex 8616e642e706870; asc uploadfire.com/hand.php;; 8: len 8; hex 800000000000042b; asc +;; 9: len 4; hex 474bfa2b; asc GK +;; 10: len 8; hex 8000000000004e24; asc N$;;
?*** (2) WAITING FOR THIS LOCK TO BE GRANTED: (任务2等待的锁)
?RECORD LOCKS space id 0 page no 843102 n bits 600 index `idx_1` of table `db/tab_test` trx id 0 677833454 lock_mode X locks rec but not gap waiting
?Record lock, heap no 395 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
?0: len 8; hex 8000000000000425; asc %;; 1: len 8; hex 800012412c66d29c; asc A,f ;; 2: len 8; hex 800000000097629c; asc b ;;
?*** WE ROLL BACK TRANSACTION (1)
?(回滚了任务1,以解除死锁)
原因分析:
当“update tab_test set state=1064,time=now() where state=1061 and time < date_sub(now(), INTERVAL 30 minute)”执行时,MySQL会使用idx_1索引,因此首先锁定相关的索引记录,因为idx_1是非主键索引,为执行该语句,MySQL还会锁定主键索引。
假设“update tab_test set state=1067,time=now () where id in (9921180)”几乎同时执行时,本语句首先锁定主键索引,由于需要更新state的值,所以还需要锁定idx_1的某些索引记录。
这样第一条语句锁定了idx_1的记录,等待主键索引,而第二条语句则锁定了主键索引记录,而等待idx_1的记录,这样死锁就产生了。
6、解决办法
拆分第一条sql,先查出符合条件的主键值,再按照主键更新记录:
?select id from tab_test where state=1061 and time < date_sub(now(), INTERVAL 30 minute);
?update tab_test state=1064,time=now() where id in(......);
5. 如何处理SQL Server死锁问题
死锁,简而言之,两个或者多个trans,同时请求对方正在请求的某个对象,导致双方互相等待。简单的例子如下:x0dx0a trans1 trans2x0dx0a ------------------------------------------------------------------------x0dx0a 1.IDBConnection.BeginTransaction 1.IDBConnection.BeginTransactionx0dx0a 2.update table A 2.update table Bx0dx0a 3.update table B 3.update table Ax0dx0a 4.IDBConnection.Commit 4.IDBConnection.Commit x0dx0a 那么,很容易看到,如果trans1和trans2,分别到达了step3,那么trans1会请求对于B的X锁,trans2会请求对于A的X锁,而二者的锁在step2上已经被对方分别持有了。由于得不到锁,后面的Commit无法执行,这样双方开始死锁。x0dx0a 好,我们看一个简单的例子,来解释一下,应该如何解决死锁问题。x0dx0a -- Batch #1x0dx0a CREATE DATABASE deadlocktestx0dx0a GOx0dx0a USE deadlocktestx0dx0a SET NOCOUNT ONx0dx0a DBCC TRACEON (1222, -1)x0dx0a -- 在SQL2005中,增加了一个新的dbcc参数,就是1222,原来在2000下,我们知道,可以执行dbcc x0dx0a --traceon(1204,3605,-1)看到所有的死锁信息。SqlServer 2005中,对于1204进行了增强,这就是1222。x0dx0a GO x0dx0a x0dx0a IF OBJECT_ID ('t1') IS NOT NULL DROP TABLE t1x0dx0a IF OBJECT_ID ('p1') IS NOT NULL DROP PROC p1x0dx0a IF OBJECT_ID ('p2') IS NOT NULL DROP PROC p2x0dx0a GOx0dx0a CREATE TABLE t1 (c1 int, c2 int, c3 int, c4 char(5000)) x0dx0a GOx0dx0a DECLARE @x intx0dx0a SET @x = 1x0dx0a WHILE (@x <= 1000) BEGINx0dx0a INSERT INTO t1 VALUES (@x*2, @x*2, @x*2, @x*2)x0dx0a SET @x = @x + 1x0dx0a ENDx0dx0a GOx0dx0a CREATE CLUSTERED INDEX cidx ON t1 (c1)x0dx0a CREATE NONCLUSTERED INDEX idx1 ON t1 (c2)x0dx0a GOx0dx0a CREATE PROC p1 @p1 int AS SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1x0dx0a GOx0dx0a CREATE PROC p2 @p1 int ASx0dx0a UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1x0dx0a UPDATE t1 SET c2 = c2-1 WHERE c1 = @p1x0dx0a GOx0dx0a 上述sql创建一个deadlock的示范数据库,插入了1000条数据,并在表t1上建立了c1列的聚集索引,和c2列的非聚集索引。另外创建了两个sp,分别是从t1中select数据和update数据。 x0dx0a 好,打开一个新的查询窗口,我们开始执行下面的query:x0dx0a -- Batch #2x0dx0a USE deadlocktestx0dx0a SET NOCOUNT ONx0dx0a WHILE (1=1) EXEC p2 4x0dx0a GOx0dx0a 开始执行后,然后我们打开第三个查询窗口,执行下面的query:x0dx0a -- Batch #3x0dx0a USE deadlocktestx0dx0a SET NOCOUNT ONx0dx0a CREATE TABLE #t1 (c2 int, c3 int)x0dx0a GOx0dx0a WHILE (1=1) BEGINx0dx0a INSERT INTO #t1 EXEC p1 4x0dx0a TRUNCATE TABLE #t1x0dx0a ENDx0dx0a GOx0dx0a 开始执行,哈哈,很快,我们看到了这样的错误信息:x0dx0a Msg 1205, Level 13, State 51, Procere p1, Line 4x0dx0a Transaction (Process ID 54) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.x0dx0a spid54发现了死锁。 x0dx0a 那么,我们该如何解决它?x0dx0a 在SqlServer 2005中,我们可以这么做:x0dx0a 1.在trans3的窗口中,选择EXEC p1 4,然后right click,看到了菜单了吗?选择Analyse Query in Database Engine Tuning Advisor。x0dx0a 2.注意右面的窗口中,wordload有三个选择:负载文件、表、查询语句,因为我们选择了查询语句的方式,所以就不需要修改这个radio option了。x0dx0a 3.点左上角的Start Analysis按钮x0dx0a 4.抽根烟,回来后看结果吧!出现了一个分析结果窗口,其中,在Index Recommendations中,我们发现了一条信息:大意是,在表t1上增加一个非聚集索引索引:t2+t1。x0dx0a 5.在当前窗口的上方菜单上,选择Action菜单,选择Apply Recommendations,系统会自动创建这个索引。x0dx0a 重新运行batch #3,呵呵,死锁没有了。x0dx0a 这种方式,我们可以解决大部分的Sql Server死锁问题。那么,发生这个死锁的根本原因是什么呢?为什么增加一个non clustered index,问题就解决了呢? 这次,我们分析一下,为什么会死锁呢?再回顾一下两个sp的写法:x0dx0a CREATE PROC p1 @p1 int AS x0dx0a SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1 x0dx0a GOx0dx0a CREATE PROC p2 @p1 int ASx0dx0a UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1x0dx0a UPDATE t1 SET c2 = c2-1 WHERE c1 = @p1x0dx0a GOx0dx0a 很奇怪吧!p1没有insert,没有delete,没有update,只是一个select,p2才是update。这个和我们前面说过的,trans1里面updata A,update B;trans2里面upate B,update A,根本不贴边啊!x0dx0a 那么,什么导致了死锁?x0dx0a 需要从事件日志中,看sql的死锁信息:x0dx0a Spid X is running this query (line 2 of proc [p1], inputbuffer “? EXEC p1 4 ?”): x0dx0a SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1x0dx0a Spid Y is running this query (line 2 of proc [p2], inputbuffer “EXEC p2 4”): x0dx0a UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1x0dx0a x0dx0a The SELECT is waiting for a Shared KEY lock on index t1.cidx. The UPDATE holds a conflicting X lock. x0dx0a The UPDATE is waiting for an eXclusive KEY lock on index t1.idx1. The SELECT holds a conflicting S lock.x0dx0a 首先,我们看看p1的执行计划。怎么看呢?可以执行set statistics profile on,这句就可以了。下面是p1的执行计划x0dx0a SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1x0dx0a |--Nested Loops(Inner Join, OUTER REFERENCES:([Uniq1002], [t1].[c1]))x0dx0a |--Index Seek(OBJECT:([t1].[idx1]), SEEK:([t1].[c2] >= [@p1] AND [t1].[c2] <= [@p1]+(1)) ORDERED FORWARD)x0dx0a |--Clustered Index Seek(OBJECT:([t1].[cidx]), SEEK:([t1].[c1]=[t1].[c1] AND [Uniq1002]=[Uniq1002]) LOOKUP ORDERED FORWARD)x0dx0a 我们看到了一个nested loops,第一行,利用索引t1.c2来进行seek,seek出来的那个rowid,在第二行中,用来通过聚集索引来查找整行的数据。这是什么?就是bookmark lookup啊!为什么?因为我们需要的c2、c3不能完全的被索引t1.c1带出来,所以需要书签查找。 x0dx0a 好,我们接着看p2的执行计划。x0dx0a UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1x0dx0a |--Clustered Index Update(OBJECT:([t1].[cidx]), OBJECT:([t1].[idx1]), SET:([t1].[c2] = [Expr1004]))x0dx0a |--Compute Scalar(DEFINE:([Expr1013]=[Expr1013]))x0dx0a |--Compute Scalar(DEFINE:([Expr1004]=[t1].[c2]+(1), [Expr1013]=CASE WHEN CASE WHEN ...x0dx0a |--Top(ROWCOUNT est 0)x0dx0a |--Clustered Index Seek(OBJECT:([t1].[cidx]), SEEK:([t1].[c1]=[@p1]) ORDERED FORWARD) x0dx0a 通过聚集索引的seek找到了一行,然后开始更新。这里注意的是,update的时候,它会申请一个针对clustered index的X锁的。x0dx0a 实际上到这里,我们就明白了为什么update会对select产生死锁。update的时候,会申请一个针对clustered index的X锁,这样就阻塞住了(注意,不是死锁!)select里面最后的那个clustered index seek。死锁的另一半在哪里呢?注意我们的select语句,c2存在于索引idx1中,c1是一个聚集索引cidx。问题就在这里!我们在p2中更新了c2这个值,所以sqlserver会自动更新包含c2列的非聚集索引:idx1。而idx1在哪里?就在我们刚才的select语句中。而对这个索引列的更改,意味着索引集合的某个行或者某些行,需要重新排列,而重新排列,需要一个X锁。x0dx0a SO???,问题就这样被发现了。x0dx0a 总结一下,就是说,某个query使用非聚集索引来select数据,那么它会在非聚集索引上持有一个S锁。当有一些select的列不在该索引上,它需要根据rowid找到对应的聚集索引的那行,然后找到其他数据。而此时,第二个的查询中,update正在聚集索引上忙乎:定位、加锁、修改等。但因为正在修改的某个列,是另外一个非聚集索引的某个列,所以此时,它需要同时更改那个非聚集索引的信息,这就需要在那个非聚集索引上,加第二个X锁。select开始等待update的X锁,update开始等待select的S锁,死锁,就这样发生鸟。 x0dx0a 那么,为什么我们增加了一个非聚集索引,死锁就消失鸟?我们看一下,按照上文中自动增加的索引之后的执行计划:x0dx0a SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1x0dx0a |--Index Seek(OBJECT:([deadlocktest].[dbo].[t1].[_dta_index_t1_7_2073058421__K2_K1_3]), SEEK:([deadlocktest].[dbo].[t1].[c2] >= [@p1] AND [deadlocktest].[dbo].[t1].[c2] <= [@p1]+(1)) ORDERED FORWARD)x0dx0a 哦,对于clustered index的需求没有了,因为增加的覆盖索引已经足够把所有的信息都select出来。就这么简单。x0dx0a 实际上,在sqlserver 2005中,如果用profiler来抓eventid:1222,那么会出现一个死锁的图,很直观的说。x0dx0a 下面的方法,有助于将死锁减至最少(详细情况,请看SQLServer联机帮助,搜索:将死锁减至最少即可。x0dx0a按同一顺序访问对象。 x0dx0a避免事务中的用户交互。 x0dx0a保持事务简短并处于一个批处理中。 x0dx0a使用较低的隔离级别。 x0dx0a使用基于行版本控制的隔离级别。 x0dx0a将 READ_COMMITTED_SNAPSHOT 数据库选项设置为 ON,使得已提交读事务使用行版本控制。 x0dx0a使用快照隔离。x0dx0a使用绑定连接。
6. sql server 2000收缩数据库总是发生死锁
出现死锁的情况:
1). 不按同一顺序访问对象。(注:出现循环)
2). 事务中的用户交互。(注:增加持有资源的时间,较多锁竞争)
3). 事务冗长并处于多个批处理中。(注:增加持有资源的时间)
4). 使用较高的隔离级别。(注:使用较低的隔离级别(例如已提交读))
5). 不使用基于行版本控制的隔离级别:2005中支持快照事务隔离和指定READ_COMMITTE
隔离级别的事务使用行版本控制,可以将读与写操作之间发生的死锁几率降至最低。
6). 不使用绑定连接。
解决办法:
使用SET LOCK_TIMEOUT timeout_period(单位为毫秒)来设定锁请求超时。默认情况下,数据库没有超时期限(timeout_period值为-1,可以用SELECT @@LOCK_TIMEOUT来查看该值,即无限期等待)。当请求锁超过timeout_period时,将返回错误。timeout_period值为0时表示根本不等待,一遇到锁就返回消息。设置锁请求超时,破环了死锁的第二个必要条件(请求与保持条件)。
压缩文件,我觉得这样比较好,
use 数据库名;
select * from sys.database_files
dbcc shrinkfile(2,1) --前者参数1代表主数据文件,2代表日志文件;后者参数是大小