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

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

问题1:为什么加WHERE条件就慢,不加反而快?

查询SQL:

WITH Temp AS(
SELECT * ,
ROW_NUMBER()OVER(ORDER BY T2.C6 DESC) AS RID
FROM TB001 AS T1
INNER JOIN TB002 AS T2
ON T1.C1=T2.C1
WHERE T1.C2>1000
AND T2.C3<99999
AND T1.C4=5
)
SELECT *
FROM Temp
WHERE RID BETWEEN 0 AND 10

开发大哥很激动地问我,对上面类似的的查询,如果没有WHERE RID BETWEEN 0 AND 10的话,查询在1秒内完成,如果有WHERE条件,执行超过30秒未结束,不带WHERE条件返回300行左右数据,WHERE条件过滤后返回10行数据,返回的数据行长度较小,可以忽略由于返回数据大小对网络和显示的影响,那问题出在那呢?稍微有点DBA经验的人都会很快找到问题根源--执行计划不对。

让我们换个简单的SQL来分析下

WITH Temp AS(
SELECT * ,
ROW_NUMBER()OVER(ORDER BY T1.C1 DESC) AS RID
FROM TB001 AS T1
WHERE T1.C2>1000
)
SELECT *
FROM Temp
WHERE RID BETWEEN 0 AND 10

让我们揣测下上面查询如何实现,假设在T1.C1有索引IX_C1,在T1.C2上有索引IX_C2。

实现方式1:

A=>针对CTE内部的查询,先利用索引IX_C2找出满足条件T1.C2>1000的数据,得到结果集U1

B=>对结果集U1按T1.C1排序,计算出U1中每行RID列的值,得到结果集U2

C=>对结果集U2查找满足RID BETWEEN 0 AND 10过滤的行,得到结果集U3

D=>将结果集U3返回

实现方式2:

A=>利用索引IX_C1按ORDER BY T1.C1 DESC来依次访问T1数据

B=>检查步骤A得到的行是否满足T1.C2>1000条件,将满足条件的结果放入结果集U1中,然后一次递增RID

C=>检查步骤B得到的结果集UI,当得到足够数据行(RID BETWEEN 0 AND 10)后停止步骤A和B

D=>将结果集U1返回

以上两种方式都能得到正确的返回结果,但是那种更好呢?

对于实现方式1,假设表T1有100W数据,如果满足T1.C2>1000的行只有20行,那么使用索引IX_C2快速找出满足条件的20行数据,然后对这20行数据排序也只会消耗很轻微的CPU资源;但如果满足T1.C2>1000的行只有99W行,那么排序就消耗大量CPU资源,从而导致查询慢。

对于实现方式2,假设表T1有100W数据,按照索引IX_C1 倒序遍历C1的值,如果遍历前50行便能查找到满足T1.C2>1000的10行数据,那么查询可以很快结束,只消耗少量的逻辑读;但如果需要遍历前99W数据才能找到满足T1.C2>1000的10行数据,那么就会消耗大量的逻辑读,从而导致查询慢。

由此,我们不难得出一个结论:没有绝对正确的执行计划,只有相对高质量的执行计划。

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

我们知道,在SQL SERVER生成执行计划时,会根据输入的参数和统计信息去预估一些步骤的影响行数和开销,寻找开销较小的执行计划,对于本篇开头提到的查询,SQL SERVER很容易受到RID BETWEEN 0 AND 10的诱惑,选择类似于实现方式2的的执行计划,而数据分布情况又恰好是针对该方式最坏的情况,就出现了我们遇到的结果,查询死慢死慢的。

类似的案例还有:

1. 查询返回数据20行,然后在此查询的基础上增加ORDER BY 和TOP(10), 结果执行效率慢了很多,于是就产生了为什么对20行数据排序取TOP会这么慢的疑惑?

2. 查询返回数据20行,在查询中分别增加SELECT TOP(20)和SELECT TOP(10000),结果SELECT TOP(10000)的比SELECT TOP(20)快很多倍,我遇到的案例有SELECT TOP(10000)在5ms内完成,然后SELECT TOP(1)的十分钟都没有结果

以上案例都有相同的操作ORDER BY+TOP,ROW_NUMBER本质上也是ORDER BY+TOP,我们知道CPU资源是服务器资源中最宝贵的资源,而对结果集排序又是一个很耗CPU资源的过程,SQL SERVER为节省CPU资源选择了一个“它”认为比较合适的执行计划,结果悲剧了。

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

针对哪位开发大哥的问题,我尝试了各种写法,在不动用临时表和索引提示的情况下,我还真搞不定这SQL,于是我来了个邪恶小招数:

WITH Temp AS(
SELECT * ,
ROW_NUMBER()OVER(ORDER BY T2.C6 DESC) AS RID
FROM TB001 AS T1
INNER JOIN TB002 AS T2
ON T1.C1=T2.C1
WHERE T1.C2>1000
AND T2.C3<99999
AND T1.C4=5
)
SELECT *
FROM Temp
WHERE RID+0 BETWEEN 0 AND 10

学术派们要开始叫嚣了,这种RID+0 BETWEEN 0 AND 10写法不科学啊,效率低下,初级程序员不懂SQL写的烂SQL啊。。。

使用RID+0来骗过查询优化器,让“它”无法估算出BETWEEN 0 AND 10需要返回的行数,这样“它”只能老老实实地“先”做CET内部的查询.

PS: 我骗得过查询优化器,骗不过开发大哥,他一直认为这个写法太BT,问了其他的DBA好几次,就是不采纳我的建议,悲催啊。

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

一个小建议:

不要见到类似WHEERE C1+10>20这种的就叫嚣不好,就喊着不能走索引的口号,看看场景再说么,万一C1上就压根没有索引呢?

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

ROW_NUMBER在实现分页行的确很好用,但是也不是所有场景都适用,这是一个真实的例子

一个查询只有两个参数@P1和@P2,代表取第@P1行到第@P2行之间的数

当@P1=0 AND @P2=1000时,消耗是这样的:

表 'XXXDetail'。扫描计数 186,逻辑读取 4922 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
表 'XXX'。扫描计数 1,逻辑读取 809 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。 SQL Server 执行时间:
CPU 时间 = 0 毫秒,占用时间 = 73 毫秒。

当@P1=7241284 AND @P2=7240285时,消耗是这样的:

表 'XXXDetail'。扫描计数 1468817,逻辑读取 35838994 次,物理读取 1 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
表 'XXX'。扫描计数 1,逻辑读取 5983509 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。 SQL Server 执行时间:
CPU 时间 = 45926 毫秒,占用时间 = 56816 毫秒。

真有份这么多页的,无语吧!!!
既然无语,我就不多做解释,说多就是眼泪,看看就好。

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

打完收工,妹子附上

曲演杂坛--蛋疼的ROW_NUMBER函数的更多相关文章

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

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

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

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

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

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

  4. 曲演杂坛--HASH的一点理解

    HASH,百度百科上做如下定义: Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列 ...

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

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

  6. 曲演杂坛--使用CTE时踩的小坑:No Join Predicate

    在一次系统优化中,意外发现一个比较“坑”的SQL,拿出来供大家分享. 生成演示数据: --====================================== --检查测试表是否存在 IF(O ...

  7. 曲演杂坛--EXISTS语句

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

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

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

  9. 曲演杂坛--Update的小测试

    今天偶然想起一个UPDATE相关的小问题,正常情况下,如果我们将UPDATE改写成与之对应的SELECT语句,其SELECT查询结果应与UPDATE的目标表存在一对一的关系,例如: 对于UPDATE语 ...

随机推荐

  1. C语言 · 奇偶判断

    问题描述 能被2整除的数称为偶数,不能被2整除的数称为奇数.给一个整数x,判断x是奇数还是偶数. 输入格式 输入包括一个整数x,0<=x<=100000000. 输出格式 如果x是奇数,则 ...

  2. Java中的多态

    1.多态:具有表现多种形态的能力的特征 父类: public abstract class Animal {public abstract void Say();} 子类: public class ...

  3. 在Linux系统下运行微信Web开发者工具

    微信Web开发者工具只有window版本和mac版本,如果想要在Linux系统下运行微信Web开发者工具,需要花费很大周折. 注:带 * 的步骤或文件为不确定是否管用的步骤或文件.本人系统为Linux ...

  4. html5 canvas常用api总结(二)--绘图API

    canvas可以绘制出很多奇妙的样式和美丽的效果,通过几个简单的api就可以在画布上呈现出千变万化的效果,还可以制作网页游戏,接下来就总结一下和绘图有关的API. 绘画的时候canvas相当于画布,而 ...

  5. JDBC Tutorials: Commit or Rollback transaction in finally block

    http://skeletoncoder.blogspot.com/2006/10/jdbc-tutorials-commit-or-rollback.html JDBC Tutorials: Com ...

  6. golang sync.WaitGroup bug

    注意,这个结构体,要是想在函数之间传来传去的话,必须要使用指针....... 这个结构体里没有 指针,这个类型可以说没有“引用特性”. 被坑了一晚上.特此记录.

  7. git多账号登录问题

    作者:白狼 出处:http://www.manks.top/git-multiply-accounts.html 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文 ...

  8. php+websocket搭建简易聊天室实践

    1.前言 公司游戏里面有个简单的聊天室,了解了之后才知道是node+websocket做的,想想php也来做个简单的聊天室.于是搜集各种资料看文档.找实例自己也写了个简单的聊天室. http连接分为短 ...

  9. Linux自动共享USB设备:udev+Samba

    一.概述 公司最近要我实现USB设备插入Ubuntu后,自动共享到网络上,能像Windows共享一样(如\\192.168.1.10)访问里面的内容,不需要写入权限.当时听完这需求,我这新人表示惊呆了 ...

  10. 微信小程序新单位rpx与自适应布局

    rpx是微信小程序新推出的一个单位,按官方的定义,rpx可以根据屏幕宽度进行自适应,在rpx出现之前,web页面的自适应布局已经有了多种解决方案,为什么微信还捣鼓出新的rpx单位?在解释这个单位前,我 ...