首页 > 代码库 > SQL Server 死锁 (Page锁)诊断

SQL Server 死锁 (Page锁)诊断

在数据库中打开死锁监测可以收集到数据库发生的死锁情况。打开的方式有2种:

1 打开1222监控

执行SQL语句:

Dbcc traceon(1222,-1);

 然后在系统日志里查看死锁的信息。

2 启动SQL Profiler(建议使用):

下面就是一个发生死锁的实例图:

下面提供对这个死锁分析思路,如有不当之处,还望大家批评指正。

一共3个问题,下面逐个回答。

第一个问题:被锁定的资源是什么?

上面写的很清楚,是一个Page 锁, 那么Page 锁是什么呢?

通常死锁是你操作A表,然后又要操作B表,而另外一个进程先操作的B表,然后等待你释放A表的锁而导致的死锁,这时看的图上面就会明确说Table Lock,而不是Page Lock。

要解释Page Lock 得先解释下什么是Page

在SQL Server 中,数据存放的时候是放在一个8K的数据单元里,这个数据单元称为 Page.

读取数据时,不是一笔一笔的读取,而是一个Page 一个Page 的读取,所以SQL Server 的文档中都会说,可能读取了一些不需要的数据。

知道了Page 之后,怎么会有Page 锁呢? Table Lock ,Row Lock 比较容易理解,但是Page Lock 就不太容易理解,实际上,这时SQL Server 对于大表采取的一种它认为性价比最好的策略。如果采取表锁,那么一旦Update 数据,则别人就不能Select 了,否则会出现数据不一致的情况(就是所谓的脏读,幻读),但是你有可能会说,你修改你的,我读取的并不是你要改的那一笔,你不要锁定我,应该采取Row Lock,但是想象一下,对于一个有100万笔数据的表来说,采取行锁,成本得有多高?所以SQL Server采取了一个中庸的方式,使用8K的数据页Page作为锁的单位,这样就平衡了并发与性能的问题,性价比最高。

定位Page

一个表里有很多Page,那么本例中锁住的Page 到底是什么呢?

执行SQL语句查询DB的名字

Select db_name(5)

 

打开这个数据库,执行如下SQL语句,可以查询这个Page 属于谁.

     select object_name(i.object_id), i.name     from sys.partitions as p     inner join sys.indexes as i on i.object_id = p.object_id and i.index_id = p.index_id     where p.partition_id = ‘72057594048544768‘  

 是一个非聚集的索引。

Page里放了什么?

目前我们已经了解了,它锁定的是一个索引的一个数据页(如果您对索引的Page 组织结构感兴趣,请参考 索引内部结构实例)。 那么锁住的2个Page里面存放了什么信息?

执行如下SQL:

DBCC TRACEON(3604)DBCC Page (DBName,1,46574417,1) 

 结果如下:

执行如下SQL:

DBCC Page (DBName,1,93865146,1) 

 结果如下:

第二个问题:是谁在锁住这些页?

查看之后发现:

1个SQL语句是Select

2个SQL 语句是Update

Select 语句为什么要锁定这2页?

本例中的Select 语句使用了本索引,使用它来快速定位,那么它根据索引的分支节点寻找,然后到指定的Page 中去读取寻找,因为是Top 500,所以,可能找到一批数据,但是还不够500,所以接着往其他页里找,直到找满500笔为止

Update 语句为什么要锁定这2页?

找到这笔数据直接修改不就完了吗?并不是的,修改一个索引的值,至少要经过如下3个步骤:

第三个问题: Exchange Event是啥意思?

本例分析总结:

某个Index 里有多个数据页,而 Update 语句正在更新这个Index 里的一笔数据,锁定了2个页,与Select 中使用的2页发生互抢,形成死锁。

解决方案:

调整语句,调整Select 吗?

让它不使用这个索引?索引建立就是为了给Select 用的,这个Select 不用,其他Select 也会用到。

调整 Update 吗?

因为更新的栏位是包含在索引中的,无论怎么调整,更新时总是会同步修改索引的。

结语:

死锁是不可避免的,我们要做的是降低发生的频率,这是我们常听到的,但是为什么呢? 如果是更新表的顺序不一致,那么应该可以通过修改提交更新SQL可以消除,但是本例呢?你打算通过什么方式让这种情况永不存在

降低发生的频率是对我们的要求,比如本例,怎么降低?

    降低SQL 执行的频率:

    发出Select 语句和Update 语句太频繁,业务上是否真的这么频繁?是否都定相同的时间来执行?比如Update和Select 都是5秒轮一次?

    提高SQL执行的效率:

Select 语句返回的数据有多少笔?能否再减少一些?返回有多少栏位?能否减少一些?

Select 语句的查询计划是否应该优化?最多3-5页面读取就应该返回结果,这个Select有多少logic read ?是否需要添加其他准确定位的索引?(难道一个Select 我们就添加一个Index吗?当然不是,这是在频繁出现这种死锁的情况使用的,因为既然频繁出现,说明这个 Select执行更频繁,很容易和Update 撞在一起。)

能不能Select 不要锁?

业务上是否可以允许小范围的脏读?

SQL Server 死锁 (Page锁)诊断