SQL Server里因丢失索引造成的死锁
2015-08-02 08:05
555 查看
在今天的文章里我想演示下SQL Server里在表上丢失索引如何引起死锁(deadlock)的。为了准备测试场景,下列代码会创建2个表,然后2个表都插入4条记录。
在我向你重现死锁前,先看下列的代码,它是个简单的UPDATE语句,在第1个表里更新一个指定行。
因为在Column2上没有索引定义,对于我们的UPDATE语句,查询优化器在执行计划里必须选择[b]表扫描(Table Scan)[/b]运算符来查找符合的记录:
![](http://images0.cnblogs.com/blog2015/750348/201508/010848455015877.png)
这就是说我们必须扫描整个堆表来找我们想更新的行。在那个情况下,SQL Server用[b]排它锁(Exclusive Lock)[/b]锁定表里的第1行。当你在不同的会话执行一个SELECT语句,引用另一个堆表里“将发生”的行,表扫描(Table Scan)运算符会阻塞,因为首先你必须读取所有堆表里“已发生”的行,即获取你查询里逻辑请求的行。
表扫描(Table Scan)默认意味这你必须扫描整个表,因此你必须在每条记录上获得[b]共享锁(Shared Lock)[/b]——即使在你逻辑上不请求的记录上。如果你用不同的顺序,在不同的会话里访问2个表,当你从同个表尝试读写时,这个情况会导致死锁情形。下面代码显示来自第1个查询的事务:
下面显示来自第2个事务的代码:
从2个事务可以看到,2个表在不同的顺序里被访问。如果时机合适,在同个时间运行这2个事务会导致死锁(deadlock)情形。假设下列的执行顺序:
在Table1上第1个事务运行UPDATE语句。
在Table2上第2个事务运行UPDATE语句。
在Table2上第1个事务运行SELECT语句。这个SELECT语句会阻塞,因为表扫描(Table Scan)运算符想要在行上获得的共享锁(Shared Lock),已经被第2个事务排它锁(exclusively lock)锁定。
在Table1上第2个事务运行SELECT语句。这个SELECT语句会阻塞,因为表扫描(Table Scan)运算符想要在行上获得的共享锁(Shared Lock),已经被第1个事务排它锁(exclusively lock)锁定。
下图演示了这个死锁情形:
![](http://images0.cnblogs.com/blog2015/750348/201508/010924310644995.png)
现在2个事务相互阻塞,因此在SQL Server里你引起了死锁。在那个情况下[b]死锁监控器(Deadlock Monitor)[/b]后台进程踢入,进行最“便宜”的事务的回滚(基于事务需要写入事务日志的字节数)。
你可以在2个表里通过为Column2提供一个索引来轻松解决这个死锁。在那个情况下SQL Server可以进行符合列的查找(Seek)运算符操作,因此当你执行SELECT语句时,可以跳过已经在索引叶子层的锁定行:
下图演示了现在的死锁情形是怎样的:
![](http://images0.cnblogs.com/blog2015/750348/201508/010948448454956.png)
使用查找操作你可以跳过索引叶子层的锁定行,你可以避免我们已经讨论过的死锁。因此当你在你的数据库看到死锁情形时,仔细看下你的索引战略(设计),这非常重要!在SQL Server里,索引一直是一个很重要的东西——始终记住这个!
感谢关注!
-- Create a table without any indexes CREATE TABLE Table1 ( Column1 INT, Column2 INT ) GO -- Insert a few record INSERT INTO Table1 VALUES (1, 1) INSERT INTO Table1 VALUES (2, 2) INSERT INTO Table1 VALUES (3, 3) INSERT INTO Table1 VALUES (4, 4) GO -- Create a table without any indexes CREATE TABLE Table2 ( Column1 INT, Column2 INT ) GO -- Insert a few record INSERT INTO Table2 VALUES (1, 1) INSERT INTO Table2 VALUES (2, 2) INSERT INTO Table2 VALUES (3, 3) INSERT INTO Table2 VALUES (4, 4) GO
在我向你重现死锁前,先看下列的代码,它是个简单的UPDATE语句,在第1个表里更新一个指定行。
-- Acquires an Exclusive Lock on the row UPDATE Table1 SET Column1 = 3 WHERE Column2 = 1
因为在Column2上没有索引定义,对于我们的UPDATE语句,查询优化器在执行计划里必须选择[b]表扫描(Table Scan)[/b]运算符来查找符合的记录:
![](http://images0.cnblogs.com/blog2015/750348/201508/010848455015877.png)
这就是说我们必须扫描整个堆表来找我们想更新的行。在那个情况下,SQL Server用[b]排它锁(Exclusive Lock)[/b]锁定表里的第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)默认意味这你必须扫描整个表,因此你必须在每条记录上获得[b]共享锁(Shared Lock)[/b]——即使在你逻辑上不请求的记录上。如果你用不同的顺序,在不同的会话里访问2个表,当你从同个表尝试读写时,这个情况会导致死锁情形。下面代码显示来自第1个查询的事务:
BEGIN TRANSACTION -- Acquires an Exclusive Lock on the row UPDATE Table1 SET Column1 = 3 WHERE Column2 = 1 -- Execute the query from Session 2... -- This query acquires an Exclusive Lock on one row from Table2... -- 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 Table2 WHERE Column2 = 3 ROLLBACK TRANSACTION GO
下面显示来自第2个事务的代码:
BEGIN TRANSACTION
-- Acquires an Exclusive Lock on the row
UPDATE Table2 SET Column1 = 5 WHERE Column2 = 2
-- Continue with the query from Session 2...
-- 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
-- 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
ROLLBACK TRANSACTION
GO
从2个事务可以看到,2个表在不同的顺序里被访问。如果时机合适,在同个时间运行这2个事务会导致死锁(deadlock)情形。假设下列的执行顺序:
在Table1上第1个事务运行UPDATE语句。
在Table2上第2个事务运行UPDATE语句。
在Table2上第1个事务运行SELECT语句。这个SELECT语句会阻塞,因为表扫描(Table Scan)运算符想要在行上获得的共享锁(Shared Lock),已经被第2个事务排它锁(exclusively lock)锁定。
在Table1上第2个事务运行SELECT语句。这个SELECT语句会阻塞,因为表扫描(Table Scan)运算符想要在行上获得的共享锁(Shared Lock),已经被第1个事务排它锁(exclusively lock)锁定。
下图演示了这个死锁情形:
![](http://images0.cnblogs.com/blog2015/750348/201508/010924310644995.png)
现在2个事务相互阻塞,因此在SQL Server里你引起了死锁。在那个情况下[b]死锁监控器(Deadlock Monitor)[/b]后台进程踢入,进行最“便宜”的事务的回滚(基于事务需要写入事务日志的字节数)。
你可以在2个表里通过为Column2提供一个索引来轻松解决这个死锁。在那个情况下SQL Server可以进行符合列的查找(Seek)运算符操作,因此当你执行SELECT语句时,可以跳过已经在索引叶子层的锁定行:
CREATE NONCLUSTERED INDEX idx_Column2 ON Table1(Column2) CREATE NONCLUSTERED INDEX idx_Column2 ON Table2(Column2) GO
下图演示了现在的死锁情形是怎样的:
![](http://images0.cnblogs.com/blog2015/750348/201508/010948448454956.png)
使用查找操作你可以跳过索引叶子层的锁定行,你可以避免我们已经讨论过的死锁。因此当你在你的数据库看到死锁情形时,仔细看下你的索引战略(设计),这非常重要!在SQL Server里,索引一直是一个很重要的东西——始终记住这个!
感谢关注!
参考文章:
https://www.sqlpassion.at/archive/2014/11/24/deadlocks-caused-by-missing-indexes-in-sql-server/相关文章推荐
- MySQL 5.5.x my.cnf参数配置优化详解
- 数据库的备份和还原
- ubuntu mysql安装和用户登录
- oracle nomount mount open直接的关系
- MySQL 5.5.x my.cnf参数配置优化详解
- 如何选择满足需求的SQL on Hadoop系统
- 访问sql server2014数据库
- Redis 配置文件
- Redis 键空间通知
- MYSQL用户管理
- Oracle 学习之RMAN(十五)恢复实战--TSPITR
- 数据库系统应用一:工资管理系统系列二
- MySQL 5.6.13 解压版(zip版)安装配置方法
- SP2-0618 SP2-0611 ORA-01919 开执行计划错误(oracle12c)
- MongoDB分布式
- Odoo(OpenERP)开发实践:通过XML-RPC接口访问Odoo数据库
- MongoDB导出
- MongoDB建立索引
- MongoDb的添加,查询,修改
- 非关系型数据定义和MongoDb的安装