一次死锁情况分析过程
2008-10-29 16:17
489 查看
一般认为,数据库死锁是发生在不同数据库对象争用时,但最近,遇到了一个情况:一个SP会执行很长时间(数据量太大),在执行过程中,需要反复插入和更新一个缓冲数据表。在SP单独工作时正常,后来用户需要及时分析数据,需要随时用这个SP处理少量的数据。这时,就发生了SP同时有多个副本在运行。会操作同一个缓冲表,由于数据有标识,所以数据不会冲突,但却常会发生死锁。经分析,认为是由于行锁的争用造成了死锁的发生。
测试过程如下:
如始化数据:
DROP TABLE TestLock
Create Table TestLock(ID CHAR(1000),
Data CHAR(1000))
INSERT INTO TestLock (ID,DATA) VALUES('1','BASHAN1')
INSERT INTO TestLock (ID,DATA) VALUES('2','BASHAN2')
INSERT INTO TestLock (ID,DATA) VALUES('3','BASHAN3')
INSERT INTO TestLock (ID,DATA) VALUES('4','BASHAN4')
INSERT INTO TestLock (ID,DATA) VALUES('5','BASHAN5')
INSERT INTO TestLock (ID,DATA) VALUES('6','BASHAN6')
INSERT INTO TestLock (ID,DATA) VALUES('7','BASHAN7')
INSERT INTO TestLock (ID,DATA) VALUES('8','BASHAN8')
INSERT INTO TestLock (ID,DATA) VALUES('9','BASHAN9')
INSERT INTO TestLock (ID,DATA) VALUES('10','BASHAN10')
INSERT INTO TestLock (ID,DATA) VALUES('11','BASHAN11')
INSERT INTO TestLock (ID,DATA) VALUES('12','BASHAN12')
INSERT INTO TestLock (ID,DATA) VALUES('13','BASHAN13')
INSERT INTO TestLock (ID,DATA) VALUES('14','BASHAN14')
INSERT INTO TestLock (ID,DATA) VALUES('15','BASHAN15')
INSERT INTO TestLock (ID,DATA) VALUES('16','BASHAN16')
INSERT INTO TestLock (ID,DATA) VALUES('17','BASHAN17')
INSERT INTO TestLock (ID,DATA) VALUES('18','BASHAN18')
INSERT INTO TestLock (ID,DATA) VALUES('19','BASHAN19')
INSERT INTO TestLock (ID,DATA) VALUES('20','BASHAN20')
go
SP_SPACEUSED TestLock
SELECT * FROM TestLock
启动一个事务:
BEGIN TRAN T2
UPDATE TestLock SET DATA= RTRIM(DATA)+'X' WHERE ID = '20'
这时,事务T2会获取一个页意向排它锁和行排它锁,
这时,在前一个事务T2还没提交事务时,执行下面的事务
启动另一个事务:(需要要另一个会话中执行)
BEGIN TRAN T1
UPDATE TestLock WITH(TABLOCKX) SET DATA= RTRIM(DATA)+'X' WHERE ID = '2'
COMMIT
则事务 T1会占用锁的情况如下:
这时,T1占用了1:9186:1 行上的排它锁,并等待1:9187:1 上的更新锁,如果这时,我们再在事务T2中申请行1:9186:1上的锁,就会发现死锁。如:
现在再执行T2剩下的事务:(原会话中)
UPDATE TestLock WITH(TABLOCKX) SET DATA= RTRIM(DATA)+'X' WHERE ID = '1'
COMMIT
则事务T2又会试图申请行1:9186:1上的锁,而T1已持用这个行上的排它锁,这时,死锁就发生了。
分析:
通过上面的测试过程,我们可以发现,在更新记录时,系统会在数据所在页、表上加意向锁,由于意向锁是相兼容的,所以不会发生等待,可以及时获取。
但对于更新数据行,系统会加上行排它锁,同时,系统在检索需要更新的数据时,需要对搜索的所有数据加上更新锁,由于排它锁和更新锁不兼容,所以T1的更新操作必须等待T2事务释放占有的排它锁。 这时如果T2事务顺利结束了,那一切都好了,但如果T2事务再去申请T1事务独占的资料时,死锁就发生了。
解决办法:
基于上分析,要解决这种情况的死锁,可行的方向有两个:隔离资源,使锁不冲突,这个具体的办法是添加索引,如在我测试的示例中,只需要对ID列添加一个聚集索引,就不会发生死锁了。
另一个方向是:实现资源的串行访问,避免事务部分占用资源,争取一次性分配完事务所需要的所有资料。对这种方式,可以在更新语句上加了表锁提示,如:
UPDATE TestLock WITH(TABLOCKX) SET DATA= RTRIM(DATA)+'X' WHERE ID = '20'
我的SP里没有启用事务,SP主要功能是分析计算而不是事务处理。SP里有大量的UPDATE语句和少量的Insert语句,几乎都是操作同一个数据表,UDATE语句相对较复杂,即需要更新,又需要统计计算(由于主要功能是分析计算,所以在统计查找数据时,使用了NOLOCK),如:
UPDATE dbo.g_CalculateChargeListBuffer
SET CombineBalance1 = b.CombineBalance1,
FROM dbo.g_CalculateChargeListBuffer a,
(
SELECT ISNULL(RefChargeCode,ChargeCode) AS ChargeCode,
CombineBalance1 = SUM(Balance1)
FROM dbo.g_CalculateChargeListBuffer WITH(NOLOCK)
WHERE VPackageID = @VPackageID
GROUP BY ISNULL(RefChargeCode,ChargeCode)
) b
WHERE a.VPackageID = @VPackageID AND a.ChargeCode = b.ChargeCode
由于SP用于分析计算,整个SP中各批处理之间没有采用事务。所以SP中的事务只会发生在同一条SQL语句执行期间。故可以认为这些事务相对较小。
同时,基于SP的作用,对系统的并发性要求不高,主要是处理性能。
重要的是,Where条件筛选列的选择性不高,创建索引效果估计不太理想。
所以我现在采用的是直接加表锁的形式解决了此问题。
不知那位网友有更好的办法,欢迎指教。
测试过程如下:
如始化数据:
DROP TABLE TestLock
Create Table TestLock(ID CHAR(1000),
Data CHAR(1000))
INSERT INTO TestLock (ID,DATA) VALUES('1','BASHAN1')
INSERT INTO TestLock (ID,DATA) VALUES('2','BASHAN2')
INSERT INTO TestLock (ID,DATA) VALUES('3','BASHAN3')
INSERT INTO TestLock (ID,DATA) VALUES('4','BASHAN4')
INSERT INTO TestLock (ID,DATA) VALUES('5','BASHAN5')
INSERT INTO TestLock (ID,DATA) VALUES('6','BASHAN6')
INSERT INTO TestLock (ID,DATA) VALUES('7','BASHAN7')
INSERT INTO TestLock (ID,DATA) VALUES('8','BASHAN8')
INSERT INTO TestLock (ID,DATA) VALUES('9','BASHAN9')
INSERT INTO TestLock (ID,DATA) VALUES('10','BASHAN10')
INSERT INTO TestLock (ID,DATA) VALUES('11','BASHAN11')
INSERT INTO TestLock (ID,DATA) VALUES('12','BASHAN12')
INSERT INTO TestLock (ID,DATA) VALUES('13','BASHAN13')
INSERT INTO TestLock (ID,DATA) VALUES('14','BASHAN14')
INSERT INTO TestLock (ID,DATA) VALUES('15','BASHAN15')
INSERT INTO TestLock (ID,DATA) VALUES('16','BASHAN16')
INSERT INTO TestLock (ID,DATA) VALUES('17','BASHAN17')
INSERT INTO TestLock (ID,DATA) VALUES('18','BASHAN18')
INSERT INTO TestLock (ID,DATA) VALUES('19','BASHAN19')
INSERT INTO TestLock (ID,DATA) VALUES('20','BASHAN20')
go
SP_SPACEUSED TestLock
SELECT * FROM TestLock
启动一个事务:
BEGIN TRAN T2
UPDATE TestLock SET DATA= RTRIM(DATA)+'X' WHERE ID = '20'
这时,事务T2会获取一个页意向排它锁和行排它锁,
spid | dbid | ObjId | IndId | Type | Resource | Mode | Status |
53 | 6 | 1701581100 | 0 | TAB | IX | GRANT | |
53 | 6 | 1701581100 | 0 | RID | 1:9197:1 | X | GRANT |
53 | 6 | 1701581100 | 0 | PAG | 1:9197 | IX | GRANT |
启动另一个事务:(需要要另一个会话中执行)
BEGIN TRAN T1
UPDATE TestLock WITH(TABLOCKX) SET DATA= RTRIM(DATA)+'X' WHERE ID = '2'
COMMIT
则事务 T1会占用锁的情况如下:
spid | dbid | ObjId | IndId | Type | Resource | Mode | Status |
52 | 6 | 1701581100 | 0 | RID | 1:9186:1 | X | GRANT |
52 | 6 | 1701581100 | 0 | RID | 1:9197:1 | U | WAIT |
52 | 6 | 1701581100 | 0 | PAG | 1:9197 | IU | GRANT |
52 | 6 | 1701581100 | 0 | PAG | 1:9186 | IX | GRANT |
现在再执行T2剩下的事务:(原会话中)
UPDATE TestLock WITH(TABLOCKX) SET DATA= RTRIM(DATA)+'X' WHERE ID = '1'
COMMIT
则事务T2又会试图申请行1:9186:1上的锁,而T1已持用这个行上的排它锁,这时,死锁就发生了。
分析:
通过上面的测试过程,我们可以发现,在更新记录时,系统会在数据所在页、表上加意向锁,由于意向锁是相兼容的,所以不会发生等待,可以及时获取。
但对于更新数据行,系统会加上行排它锁,同时,系统在检索需要更新的数据时,需要对搜索的所有数据加上更新锁,由于排它锁和更新锁不兼容,所以T1的更新操作必须等待T2事务释放占有的排它锁。 这时如果T2事务顺利结束了,那一切都好了,但如果T2事务再去申请T1事务独占的资料时,死锁就发生了。
解决办法:
基于上分析,要解决这种情况的死锁,可行的方向有两个:隔离资源,使锁不冲突,这个具体的办法是添加索引,如在我测试的示例中,只需要对ID列添加一个聚集索引,就不会发生死锁了。
另一个方向是:实现资源的串行访问,避免事务部分占用资源,争取一次性分配完事务所需要的所有资料。对这种方式,可以在更新语句上加了表锁提示,如:
UPDATE TestLock WITH(TABLOCKX) SET DATA= RTRIM(DATA)+'X' WHERE ID = '20'
我的SP里没有启用事务,SP主要功能是分析计算而不是事务处理。SP里有大量的UPDATE语句和少量的Insert语句,几乎都是操作同一个数据表,UDATE语句相对较复杂,即需要更新,又需要统计计算(由于主要功能是分析计算,所以在统计查找数据时,使用了NOLOCK),如:
UPDATE dbo.g_CalculateChargeListBuffer
SET CombineBalance1 = b.CombineBalance1,
FROM dbo.g_CalculateChargeListBuffer a,
(
SELECT ISNULL(RefChargeCode,ChargeCode) AS ChargeCode,
CombineBalance1 = SUM(Balance1)
FROM dbo.g_CalculateChargeListBuffer WITH(NOLOCK)
WHERE VPackageID = @VPackageID
GROUP BY ISNULL(RefChargeCode,ChargeCode)
) b
WHERE a.VPackageID = @VPackageID AND a.ChargeCode = b.ChargeCode
由于SP用于分析计算,整个SP中各批处理之间没有采用事务。所以SP中的事务只会发生在同一条SQL语句执行期间。故可以认为这些事务相对较小。
同时,基于SP的作用,对系统的并发性要求不高,主要是处理性能。
重要的是,Where条件筛选列的选择性不高,创建索引效果估计不太理想。
所以我现在采用的是直接加表锁的形式解决了此问题。
不知那位网友有更好的办法,欢迎指教。
相关文章推荐
- 记一次公司仓库数据库服务器死锁过程
- 一次SQL优化分析的全过程
- 一次Linux系统被攻击的分析过程
- 一次完整的HTTP事务过程分析
- sqlserver 通用存储过程分页代码(附使用ROW_NUMBER()和不使用ROW_NUMBER()两种情况性能分析)
- 记一次大压力测试下redis主从失败的分析过程
- 一次Linux系统被攻击的分析过程
- 记录一次OOM分析过程
- lftp连接异常情况分析过程
- 多线程——死锁产生的条件和过程分析
- MySQL Innodb表导致死锁日志情况分析与归纳
- MySQL redo死锁问题排查及解决过程分析
- 记录一次系统调用慢的 分析过程
- 分享一次分析/解决支付应用性能问题的全过程,细节和采用的工具和方法 推荐
- 记一次mysql死锁的原因分析和解决方法
- 记一次公司仓库数据库服务器死锁过程及解决办法
- 一次Linux系统被攻击的分析过程
- 一次 read by other session 的处理过程--数据走索引需要看索引字段的数据分布情况
- 查看数据库死锁情况的存储过程
- 一次查询耗时的分析过程