在一次系统优化中,意外发现一个比较“坑”的SQL,拿出来供大家分享。

生成演示数据:

--======================================
--检查测试表是否存在
IF(OBJECT_ID('TB2002') IS NOT NULL)
BEGIN
DROP TABLE TB2002
END
GO
--============================
--生成测试数据并创建索引
SELECT
IDENTITY(INT,1,1) AS ID,
*
INTO TB2002
FROM sys.columns C
GO
CREATE UNIQUE CLUSTERED INDEX IDX_ID
ON TB2002
(
ID
)
GO
CREATE INDEX IDX_column_id
ON TB2002
(
column_id
)

执行查询:

SELECT TOP(100) *  FROM TB2002
WHERE column_id=1

上面查询虽然列column_id上有索引,但由于该列的选择性不高,查询优化引擎根据预估行数生成“使用表扫描”的执行计划:

针对此测试环境,表扫描的确是最优的查询方式,但生产环境中我们经常遇到此类问题,由于统计信息或预估行数导致执行计划不优的情况,通常我们需要通过改写SQL来“让”查询优化引擎生成我们期望的查询方式,因此我将查询SQL优化为:

WITH T1 AS (
SELECT TOP(100) ID AS RID FROM TB2002
WHERE column_id=1
)
SELECT* FROM TB2002 WITH(FORCESEEK)
WHERE ID IN (SELECT RID FROM T1)

查询生成的执行计划为:

查询先按照索引IDX_column_id来进行查找,再按照IDX_ID进行KEY LOOKUP,这样避免了“表扫描”操作。

当然以上都是是今天的重点,重点在于我手抖了,在优化过程中一不小心漏瞧了一个字母,于是悲剧粗线了。

漏敲一个字母的SQL为:

WITH T1 AS (
SELECT TOP(100) ID AS RID FROM TB2002
WHERE column_id=1
)
SELECT* FROM TB2002
WHERE ID IN (SELECT ID FROM T1)

生成执行计划为:

先不考虑执行计划中的红叉叉,查看返回数据,我们会发现“整表的数据被返回啦”,这是什么鬼?

理论上CTE的结果集中只有RID一列,那么SELECT ID FROM T1 这个子查询应该会执行失败,我们执行以下查询

WITH T1 AS (
SELECT TOP(100) ID AS RID FROM TB2002
WHERE column_id=1
)

执行会得到以下错误:

消息 207,级别 16,状态 1,第 5 行
列名 'ID' 无效。

但对那个漏敲一个字母的SQL,查询优化引擎“赤裸裸”地忽略掉这个错误,在Nested Loops运算时只有一个“No Join Predicate”的警告,Nested Loops操作描述为“对于顶部(外部)输入的每一行,扫描底部(内部)输入,然后输出匹配的行。”,查询进行表扫描,得到一个“整表数据”的结果集T1,然后准备对结果集T1中的每一行到“CTE的结果集”中进行匹配,由于“CTE的结果集”中没有ID列,于是“莫名其妙”地认为所有行都匹配上,将整表数据都返回啦。

就好比警察抓到了一帮人,打算“在逃罪犯”系统里依次匹配每个人是不是“逃犯”,结果“在逃罪犯”系统蓝屏了,于是抓到的这帮人全成了“逃犯”,通通拉出去死啦死啦滴,还能好好玩耍了么?

--=================================================================================

群里兄弟补充,在临时表里同样有类似问题:

测试代码:

SELECT  IDENTITY( INT,1,1 ) AS ID ,
*
INTO TB2002
FROM sys.columns C
GO
SELECT TOP 1
id AS ROWID
INTO #tmp
FROM TB2002
GO
SELECT *
FROM TB2002
WHERE id IN ( SELECT ID
FROM #tmp )

--=================================================================================

惨痛教训:由于优化的是DELETE 语句,本来想着通过CTE使用NOLOCK将满足条件的ID查找出来再按照ID进行删除,检查完过滤条件没有问题,直接执行导致整表数据被删除,幸好该操作没有影响业务,并有完整备份和日志备份,最终使用“指定时间点还原”+STANDBY的方式找回数据,但想想也是后怕!建议有类似习惯的童鞋在做此类操作时,先将DELETE 修改为SELECT,确保返回数据是要删除数据后,再执行DELETE。

--=================================================================================

照例是妹子镇贴和压惊

曲演杂坛--使用CTE时踩的小坑:No Join Predicate的更多相关文章

  1. 曲演杂坛--一条DELETE引发的思考

    原文:曲演杂坛--一条DELETE引发的思考 场景介绍: 我们有一张表,专门用来生成自增ID供业务使用,表结构如下: CREATE TABLE TB001 ( ID ,) PRIMARY KEY, D ...

  2. 曲演杂坛--蛋疼的ROW_NUMBER函数

    使用ROW_NUMBER来分页几乎是家喻户晓的东东了,而且这东西简单易用,简直就是程序员居家必备之杀器,然而ROW_NUMBER也不是一招吃遍天下鲜的无敌BUG般存在,最近就遇到几个小问题,拿出来供大 ...

  3. 曲演杂坛--当ROW_NUMBER遇到TOP

    值班期间研发同事打来电话,说应用有超时,上服务器上检查发现有SQL大批量地执行,该SQL消耗IO资源较多,导致服务器存在IO瓶颈,细看SQL,发现自己都被整蒙了,不知道这SQL是要干啥,处理完问题赶紧 ...

  4. 曲演杂坛--特殊字符/生僻字与varchar

    对于中文版的SQL SERVER,默认安装后使用的默认排序规则为Chinese_PRC_CI_AS,在此排序规则下,使用varchar类型来可以“正常存取”存放中文字符以及一些东南亚国家的字符,同时v ...

  5. 曲演杂坛--为什么SELECT语句会被其他SELECT阻塞?

    很多刚入门的DBA在捕获阻塞得时候,会问这么一个问题“为什么这个SELECT语句被那个SELECT语句阻塞了,难道不是共享锁么?” 让我们来做个小测试,首先准备一些测试数据: --========== ...

  6. 曲演杂坛--EXISTS语句

    通常在我写EXISTS语句时,我会写成IF EXISTS(SELECT TOP(1) 1 FROM XXX),也没细细考究过为什么要这么写,只是隐约认为这样写没有啥问题,那今天就深究下吧! 首先准备测 ...

  7. 曲演杂坛--SQLCMD下执行命令失败但没有任何错误提示的坑

    今天使用SQLCMD导入到SQL SERVER数据库中,看着数据文件都成功执行,但是意外发现有一个文件数据没有成功导入,但执行不报错,很容易导致问题被忽略. 使用存在问题的文件做下测试,从界面上看几行 ...

  8. 曲演杂坛--使用TRY CATCH应该注意的一个小细节

    群里一个朋友遇到一个TRY CATCH的小问题,测试后发现是自己从来没有考虑的情况,写篇blog加深下印象 --============================================ ...

  9. 曲演杂坛--使用ALTER TABLE修改字段类型的吐血教训

    --===================================================================== 事件起因:开发发现有表插入数据失败,查看后发现INT类型 ...

随机推荐

  1. 《Java4Android》视频学习笔记——抽象类和抽象函数

    抽象函数的语法特征 1.类是抽象的,对象是具体的.面向对象里面有个非常重要的特征:先抽象,后具体. 2.只有函数的定义,而没有函数体的函数叫做抽象函数. abstract void fun(); ( ...

  2. Android总结篇系列:Android Service

    Service通常总是称之为“后台服务”,其中“后台”一词是相对于前台而言的,具体是指其本身的运行并不依赖于用户可视的UI界面,因此,从实际业务需求上来理解,Service的适用场景应该具备以下条件: ...

  3. Python WMI获取Windows系统信息 监控系统

    #!/usr/bin/env python # -*- coding: utf-8 -*- #http://www.cnblogs.com/liu-ke/ import wmi import os i ...

  4. ssh默认端口更改后,如何正常使用git?

    由于安全或者其它原因,我们可能会修改默认的SSH服务端口号,默认情况下,已有的git项目在pull或者push的时候会报错. 现在假设原来的项目的remote设置为git@domain.com:Pro ...

  5. 如何创建C# Closure ?

    JavaScript中一个重要的概念就是闭包,闭包在JavaScript中有大量的应用,但是你知道么?C#也可以创建Closure.下面就介绍一下如何在C#中创建神奇的闭包. 在这之前,我们必须先知道 ...

  6. GifShot - 创建动态 GIF 的 JavaScript 库

    GifShot 是一个可以创建流媒体,视频或图像的 GIF 动画的 JavaScript 库.该库的客户端特性使其非常便携,易于集成到几乎任何网站.利用最先进的浏览器 API ,包括 WebRTC , ...

  7. 65行 JavaScript 代码实现 Flappy Bird 游戏

    飞扬的小鸟(Flappy Bird)无疑是2014年全世界最受关注的一款游戏.这款游戏是一位来自越南河内的独立游戏开发者阮哈东开发,形式简易但难度极高的休闲游戏,很容易让人上瘾. 这里给大家分享一篇这 ...

  8. JavaScript入门篇QA总结

    Q1:JS可以放在哪个位置?A1:1.放在<head>标签中,用<script type="text/javascript"></script> ...

  9. 原生andriod浏览器回退后dom(click)事件全体失效问题探究

    问题描述 今天同事遇到一个神一样的BUG: 在原生浏览器下,为dom元素绑定一个click事件,其中有个a标签外链,点击a后进入其他页面,点击浏览器后退后,页面点击事件全体失效! 我于是用ios测了下 ...

  10. Hybrid框架UI重构之路:六、前端那点事儿(Javascript)

    上文回顾 :Hybird框架UI重构之路:五.前端那点事儿(HTML.CSS) 这里讲述在开发的过程中,一些JS的关键点. 换肤 对于终端的换肤,我之前一篇文章有说了我的想法. 请查看:http:// ...