业务SQL那些事--慎用LIMIT

业务SQL那些事--慎用LIMIT

在业务中使用LIMIT限制SQL返回行数是很常见的事情,但如果不知道其中可能的坑或者说真正执行逻辑,就可能会使SQL执行非常慢,严重影响性能。

LIMIT OFFSET, ROW_COUNT实现分页

业务反映一条SQL执行非常慢。简单分析,例如下面的schema与SQL(演示 databae:PostgreSQL):

create table t(c1 varchar(20) primary key, c2 int);

select * from t where c1 > '20150224' and c1 < '20160706' and c2 > 1 and c2 <500000000 order by c1 offset $offset limit 5000;(limit $offset, 5000)

其中offset的从0开始,5000递增,最大可以到200W。SQL执行时间就会随着offset的值增加而增加,最终达到业务不可承受的程度。

这条SQL因为主键有序所以省去了order by的SORT,但SQL访问表的时候依然至少需要访问$offset + 5000行数据,扫描行数随着offset增加而增加。而且这是至少需要访问的数据量,那么不难理解为什么SQL会随着offset变大而变慢。

业务是用这条语句实现分页功能,其分页的order为c1就是表的主键。所以对于查询条件可能访问大量数据的SQL应该记录last_id来实现分页。改为如下SQL,last_id初始值为'20150224',然后每次获取数据后记录最后一行的c1作为下次的last_id。

select * from t where c1 > $last_id and c1 < '20160706' and c2 > 1 and c2 <500000000 order by c1 limit 5000;

LIMIT ROW_COUNT会性能差

业务遇到一条包含有LIMIT 0, 15的SQL执行时间超过预期。简单分析,schema与SQL如下:

create table t(c1 int, c2 int, c3 int, c4 int, primary key(c1));

create index t_c2_c4_c3 on t(c2, c4, c3);

select * from t where c4 = 1 and c3 <> 9 and c2 > 1 and c2 < 1000000 order by c3, c2 offset 0 limit 15;(limit 0, 15)

业务虽然建立了索引,同时在索引前缀上有约束条件,但是由于满足约束条件的行非常多,同时order by的column不是索引ordering的前缀,所以table层依然需要访问所有满足索引条件的行,在过滤后进行SORT操作。Plan如下:

test=# explain verbose select * from t where c4 = 1 and c3 <> 9 and c2 > 1 and c2 < 1000000 order by c3, c2 offset 0 limit 15;
+----------------------------------------------------------------------------------------+
| QUERY PLAN |
+----------------------------------------------------------------------------------------+
| Limit  (cost=8.29..8.29 rows=1 width=16)   |
|   Output: c1, c2, c3, c4   |
|   ->  Sort  (cost=8.29..8.29 rows=1 width=16)  |
|       Output: c1, c2, c3, c4 |
|       Sort Key: t.c3, t.c2   |
|       ->  Index Scan using t_c2_c4_c3 on public.t  (cost=0.15..8.28 rows=1 width=16) |
|           Output: c1, c2, c3, c4   |
|           Index Cond: ((t.c2 > 1) AND (t.c2 < 1000000) AND (t.c4 = 1)) |
|           Filter: (t.c3 <> 9)  |
+----------------------------------------------------------------------------------------+

和业务方了解后,c3的值只有3个(0,1,9),即c3 <> 9可以改写为 c3 in (0,1)。同时由于c4是定值,考虑到其他SQL对c4列的使用,决定让业务建立index(c4, c3, c2)。在PostgreSQL中如下:

test=# explain verbose select * from t where c4 = 1 and c3 in(0,1)
 and c2 > 1 and c2 < 1000000 order by c3, c2 offset 0 limit 15;
+----------------------------------------------------------------------------------+
| QUERY PLAN   |
+----------------------------------------------------------------------------------+
| Limit  (cost=0.15..8.28 rows=1 width=16) |
|   Output: c1, c2, c3, c4 |
|   ->  Index Scan using t_c4_c3_c2 on public.t  (cost=0.15..8.28 rows=1 width=16) |
|       Output: c1, c2, c3, c4   |
|       Index Cond: ((t.c4 = 1) AND (t.c2 > 1) AND (t.c2 < 1000000)) |
|       Filter: (t.c3 = ANY ('{0,1}'::integer[]))|
+----------------------------------------------------------------------------------+

省去了SORT的代价,同时TABLE只需要访问满足约束条件的15行数据。

不过比较遗憾,演示的PostgreSQL没有能利用filter: c3 in (0,1)条件对(c4,c3,c2)生成两个查询范围(1,0,1)~(1,0,1000000)和(1,1,1)~(1,1,1000000),即"C4"=1 AND ("C3"=0 OR "C3"=1) AND "C2">1 AND "C2"<1000000。

时间: 2024-08-28 13:09:17

业务SQL那些事--慎用LIMIT的相关文章

如何在SQL Server中实现 Limit m,n 的功能_Mysql

在MySQL中,可以用 Limit 来查询第 m 列到第 n 列的记录,例如: 复制代码 代码如下: select * from tablename limit m, n 但是,在SQL Server中,不支持 Limit 语句.怎么办呢?解决方案:虽然SQL Server不支持 Limit ,但是它支持 TOP.我们以SQL Server 2005为例,就以它自带的示范数据库 AdventureWorks 作为测试数据: 复制代码 代码如下: select id from tablename

ROW_NUMBER SQL Server 2005的LIMIT功能实现(ROW_NUMBER()排序函数)

语法: ROW_NUMBER() OVER([ <partition_by_clause>] <order_by_clause>) 备注: ORDER BY子句可确定在特定分区中为行分配唯一ROW_NUMBER的顺序. 参数: <partition_by_clause> 将FROM子句生成的结果集划入应用了ROW_NUMBER函数的分区. <order_by_clause> 确定将ROW_NUMBER值分配给分区中的行的顺序.有关详细信息,请参阅ORDER

MySQL性能优化 出题业务SQL优化_Mysql

先简单介绍一下项目背景.这是一个在线考试练习平台,数据库使用MySQL,表结构如图所示: Question是存储题目的表,数据量在3万左右.AnswerResult表是存储用户作答结果的表,分表之后单表记录大概在300万-400万. 需求:根据用户的作答结果出练习卷,题目的优先级为:未做过的题目>只做错的题目>做错又做对的题目>只做对的题目. 在"做错又做对的题目"中,会按错误次数和正确次数的比例进行权重计算,比如:A.做错10次,做对100次:B.做错10次,做对2

mssql 实现mysql limit sql语句

mssql 实现mysql教程 limit sql语句 在mysql中,可以用 limit 来查询第 m 列到第 n 列的记录,例如: select * from tablename limit m, n 但是,在sql server中,不支持 limit 语句.怎么办呢? 解决方案: 虽然sql server不支持 limit ,但是它支持 top. 我们以sql server 2005为例,就以它自带的示范数据库教程 adventureworks 作为测试数据: select address

PHPcmsv9 get标签 sql语句limit无效及num和rows属性无效问题解决办法

PHPcms v9 get标签sql语句limit无效及num和rows属性无效问题至今也已经是一个老问题了,网络上也有一些表面的解决办法,但是逐日者并没有找到从底层,层模板标签解析及模板引擎代码上真正解决此问题的办法和解释.今天在使用get标签sql语句调用数据的时候当然也遇到了此问题,经过研究找到了问题的原因并且整理出本文希望对php爱好者有所帮助. 错误描述:我想从数据表中调取最新的6条数据!  代码如下 复制代码      <dl class="about_text1"&

【SQL】使用分析函数与关联子查询的比较

由于业务需要,需实现如下功能的sql语句:查询出一个表T中每个不同id值所对应的最大val值.---构造环境. SQL> insert into t values (1,2,1); 已创建 1 行. SQL> insert into t values (1,2,3); 已创建 1 行. SQL> insert into t values (1,3,4); 已创建 1 行. SQL> insert into t values (1,3,5); 已创建 1 行. SQL> ins

通过sql语句获取数据库的基本信息

数据|数据库|语句 您可以通过以下几个sql 可以立即了解系统的状况和数据库的状态(仅献给习惯使用sql/plus的人 呵呵 )(1) 查询oracle 的物理结构1.1. 查看数据文件SQL> select name from v$datafile;NAME---------------------------------------------/u05/dbf/PROD/system_01.dbf/u05/dbf/PROD/system_01.dbf  (系统数据文件)/u06/dbf/PR

MySQL 分页sql语句练习

[/align][align=left] 取出sql表中第31到40的记录(以自动增长ID为主键)  select top 10  * from t where id not in (select top 30 id from t order by id ) order by id;  select top 10 * from t where id in (select top 40 id from t order by id ) order by id desc ;  mysql方案:sele

select-Sql语句 limit 使用,查询某一行到全部数据

问题描述 Sql语句 limit 使用,查询某一行到全部数据 查询某一行到结束的所有数据.网上看了很多SELECT * FROM table LIMIT 95,-1; 是错的.有说查SELECT COUNT(*)FROM table 替换-1.还有其他方法能做到吗? 解决方案 SQL语句中的limit使用方法 解决方案二: 先order by 呢 没测试不知道对不? 解决方案三: 楼主大人,可以通过逆向思维去完成这样的题目哟,首先我们将前面需要排除的0到n行的id查出来,然后通过not exis