SQL Server里因丢失索引造成的死锁

原文:SQL Server里因丢失索引造成的死锁

在今天的文章里我想演示下SQL Server里在表上丢失索引如何引起死锁(deadlock)的。为了准备测试场景,下列代码会创建2个表,然后2个表都插入4条记录。

 1 -- Create a table without any indexes
 2 CREATE TABLE Table1
 3 (
 4     Column1 INT,
 5     Column2 INT
 6 )
 7 GO
 8
 9 -- Insert a few record
10 INSERT INTO Table1 VALUES (1, 1)
11 INSERT INTO Table1 VALUES (2, 2)
12 INSERT INTO Table1 VALUES (3, 3)
13 INSERT INTO Table1 VALUES (4, 4)
14 GO
15
16 -- Create a table without any indexes
17 CREATE TABLE Table2
18 (
19     Column1 INT,
20     Column2 INT
21 )
22 GO
23
24 -- Insert a few record
25 INSERT INTO Table2 VALUES (1, 1)
26 INSERT INTO Table2 VALUES (2, 2)
27 INSERT INTO Table2 VALUES (3, 3)
28 INSERT INTO Table2 VALUES (4, 4)
29 GO

在我向你重现死锁前,先看下列的代码,它是个简单的UPDATE语句,在第1个表里更新一个指定行。

1 -- Acquires an Exclusive Lock on the row
2 UPDATE Table1 SET Column1 = 3 WHERE Column2 = 1

因为在Column2上没有索引定义,对于我们的UPDATE语句,查询优化器在执行计划里必须选择表扫描(Table Scan)运算符来查找符合的记录:

这就是说我们必须扫描整个堆表来找我们想更新的行。在那个情况下,SQL Server用排它锁(Exclusive Lock)锁定表里的第1行。当你在不同的会话执行一个SELECT语句,引用另一个堆表里“将发生”的行,表扫描(Table Scan)运算符会阻塞,因为首先你必须读取所有堆表里“已发生”的行,即获取你查询里逻辑请求的行。

-- This query now requests a Shared Lock, but get‘s blocked, because the other session/transaction has an Exclusive Lock on one row, that is currently updated
SELECT Column1 FROM Table1
WHERE Column2 = 4

表扫描(Table Scan)默认意味这你必须扫描整个表,因此你必须在每条记录上获得共享锁(Shared Lock)——即使在你逻辑上不请求的记录上。如果你用不同的顺序,在不同的会话里访问2个表,当你从同个表尝试读写时,这个情况会导致死锁情形。下面代码显示来自第1个查询的事务:

 1 BEGIN TRANSACTION
 2
 3 -- Acquires an Exclusive Lock on the row
 4 UPDATE Table1 SET Column1 = 3 WHERE Column2 = 1
 5
 6 -- Execute the query from Session 2...
 7 -- This query acquires an Exclusive Lock on one row from Table2...
 8
 9 -- This query now requests a Shared Lock, but get‘s blocked, because the other session/transaction has an Exclusive Lock on one row, that is currently updated
10 SELECT Column1 FROM Table2
11 WHERE Column2 = 3
12
13 ROLLBACK TRANSACTION
14 GO

下面显示来自第2个事务的代码:

 1 BEGIN TRANSACTION
 2
 3 -- Acquires an Exclusive Lock on the row
 4 UPDATE Table2 SET Column1 = 5 WHERE Column2 = 2
 5
 6 -- Continue with the query from Session 2...
 7 -- This query now requests a Shared Lock, but get‘s blocked, because the other session/transaction has an Exclusive Lock on one row, that is currently updated
 8
 9 -- This query now requests a Shared Lock, but get‘s blocked, because the other session/transaction has an Exclusive Lock on one row, that is currently updated
10 SELECT Column1 FROM Table1
11 WHERE Column2 = 4
12
13 ROLLBACK TRANSACTION
14 GO

从2个事务可以看到,2个表在不同的顺序里被访问。如果时机合适,在同个时间运行这2个事务会导致死锁(deadlock)情形。假设下列的执行顺序:

  1. 在Table1上第1个事务运行UPDATE语句。
  2. 在Table2上第2个事务运行UPDATE语句。
  3. 在Table2上第1个事务运行SELECT语句。这个SELECT语句会阻塞,因为表扫描(Table Scan)运算符想要在行上获得的共享锁(Shared Lock),已经被第2个事务排它锁(exclusively lock)锁定。
  4. 在Table1上第2个事务运行SELECT语句。这个SELECT语句会阻塞,因为表扫描(Table Scan)运算符想要在行上获得的共享锁(Shared Lock),已经被第1个事务排它锁(exclusively lock)锁定。

下图演示了这个死锁情形:

现在2个事务相互阻塞,因此在SQL Server里你引起了死锁。在那个情况下死锁监控器(Deadlock Monitor)后台进程踢入,进行最“便宜”的事务的回滚(基于事务需要写入事务日志的字节数)。

你可以在2个表里通过为Column2提供一个索引来轻松解决这个死锁。在那个情况下SQL Server可以进行符合列的查找(Seek)运算符操作,因此当你执行SELECT语句时,可以跳过已经在索引叶子层的锁定行:

1 CREATE NONCLUSTERED INDEX idx_Column2 ON Table1(Column2)
2 CREATE NONCLUSTERED INDEX idx_Column2 ON Table2(Column2)
3 GO

下图演示了现在的死锁情形是怎样的:

使用查找操作你可以跳过索引叶子层的锁定行,你可以避免我们已经讨论过的死锁。因此当你在你的数据库看到死锁情形时,仔细看下你的索引战略(设计),这非常重要!在SQL Server里,索引一直是一个很重要的东西——始终记住这个!

感谢关注!

时间: 2024-10-06 11:59:32

SQL Server里因丢失索引造成的死锁的相关文章

汇总SQL Server里的相关运算符、子句、谓词等

汇总SQL Server里的相关运算符.子句.谓词等 (后续我会往后追加并不断对现有的进行完善和扩展) ◆ TOP1)TOP一般与ORDER BY结合使用,否则TOP出来的结果集没太大意义,除非您另有它意. 2)TOP返回数可以是变量,但必须用括号括入3)结合WITH TIES谓词选项,如果您返回4行,但最后1行有2条相同的结果,那么您TOP 4,最后1行就只是随意返回1行,保证不了结果集的正确性,如果您指定了WITH TIES, 则返回5行,ORDER BY后将最后2条相同的结果都返回,用法:

SQL Server里等待统计(Wait Statistics)介绍

在今天的文章里我想详细谈下SQL Server里的统计等待(Wait Statistics),还有她们如何帮助你立即为什么你的SQL Server当前很慢.一提到性能调优,对我来说统计等待是SQL Server了最重要的概念. 查询为什么等待 在SQL Server里每次你执行1个查询,查询总需要等待.什么?查询总需要等待?是的,你没有看错:但给你执行1个查询时,查询总需要等待.为什么查询需要等待的原因是SQL Server通过所谓的等待统计(Wait Statistics)来跟踪的.在我进入等

SQL Server里的INTERSECT

在今天的文章里,我想讨论下SQL Server里的INTERSECT设置操作.INTERSECT设置操作彼此交叉2个记录集,返回2个集里列值一样的记录.下图演示了这个概念. INTERSECT与INNER JOIN 你会发现,它和2个表间的INNER JOIN几乎一样.但今天我会介绍它们之间的一些重要区别.让我们从创建作为输入的2个简单表开始. 1 -- Create the 1st table 2 CREATE TABLE t1 3 ( 4 Col1 INT, 5 Col2 INT, 6 Co

SQL Server里在文件组间如何移动数据?

平常我不知道被问了几次这样的问题:“SQL  Server里在文件组间如何移动数据?“你意识到这个问题:你只有一个主文件组的默认配置,后来围观了“SQL Server里的文件和文件组”后,你知道,有多个文件的自定义文件组会是个更好的主意.但你现在如何从主文件组里移动现有数据到新加的文件组? 这篇文章的目的是向你展示你如何在文件组间移动数据.首先我会谈下聚集和非聚集索引,然后我会谈下如何在堆表里移动数据.让我们开始吧! 移动聚集和非聚集索引 一般来说在你的表上通常应该有一个聚集索引.有了现存的聚集

SQL Server里ORDER BY的歧义性

在今天的文章里,我想谈下SQL Server里非常有争议和复杂的话题:ORDER BY子句的歧义性. 视图与ORDER BY 我们用一个非常简单的SELECT语句开始. 1 -- A very simple SELECT statement 2 SELECT * FROM Person.Person 3 ORDER BY LastName 4 GO 从刚才列出的代码你可以看到,我们只想从Person.Person表以LastName列排序返回记录.因为我们想能尽可能简单的重用那个SQL语句,最后

SQL Server里的闩锁介绍

在今天的文章里我想谈下SQL Server使用的更高级的,轻量级的同步对象:闩锁(Latch).闩锁是SQL Server存储引擎使用轻量级同步对象,用来保护多线程访问内存内结构.文章的第1部分我会介绍SQL Server里为什么需要闩锁,在第2部分我会给你介绍各个闩锁类型,还有你如何能对它们进行故障排除. 为什么我们需要闩锁? 闩锁首次在SQL Server 7.0里引入,同时微软首次引入了行级别锁(row-level locking).对于行级别锁引入闩锁的概念是非常重要的,不然的话在内存中

SQL Server和Oracle数据库索引介绍

SQL Server和Oracle数据库索引介绍 1 SQL Server中的索引 索引是与表或视图关联的磁盘上结构,可以加快从表或视图中检索行的速度.索引包含由表或视图中的一列或多列生成的键.这些键存储在一个结构(B 树)中,使 SQL Server 可以快速有效地查找与键值关联的行. 表或视图可以包含以下类型的索引: 聚集索引 聚集索引根据数据行的键值在表或视图中排序和存储这些数据行.索引定义中包含聚集索引列.每个表只能有一个聚集索引,因为数据行本身只能按一个顺序排序. 只有当表包含聚集索引

sql server 里的文件和文件组使用

转自:https://www.cnblogs.com/woodytu/p/5821827.html 在今天的文章里,我想谈下SQL Server里非常重要的话题:SQL Server如何处理文件的文件组.当你用CREATE DATABASE命令创建一个简单的数据库时,SQL Server为你创建2个文件: 一个数据文件(.mdf) 一个事务日志文件(.ldf) 数据文件本身在有且只有一个主文件组里创建.默认情况下,在主文件组里,SQL Server存储素有的数据(用户表,系统表等).那有额外的文

mysql,sql server,oracle 唯一索引字段是否允许出现多个 null 值?

最近一个项目,涉及到sql server 2008,因为业务需求,希望建立一个唯一索引,但是发现在sql server中,唯一索引字段不能出现多个null值,下面是报错信息: CREATE UNIQUE NONCLUSTERED INDEX weixin_openid_ui ON Users(weixin_openid); 因为发现对象名称 'dbo.Users' 和索引名称 'weixin_openid_ui' 有重复的键,所以 CREATE UNIQUE INDEX 语句终止.重复的键值为