缺陷的背后---LIMIT M,N 分页查找
一、问题发现篇
最近组内做了一次典型缺陷分享时,翻阅2018年的缺陷,找到了一个让我觉得“有料”的bug(别的同事测试发现的),先大致简单的描述下这个问题:
- 需要实现的功能:从一个DB库同步某一段时间的数据到另一个DB库(简化后的需求)。
- 问题描述:一次同步20w条符合记录的数据,程序同步完成后,丢数据5条。
- 问题定位:加载数据的sql,考虑到数据量大,使用了limit M,N的方法来分页加载数据,大致如下:
select * from test where Fmodify_time >= '2018-12-12 05:00:00'and Fmodify_time <= '2018-12-12 05:01:00' limit 0,2;
开发哥哥定位问题产生的原因是因为20w的数据里,存在大量的Fmodify_time 值相同的记录,由于未排序,导致这类数据服务器返回时会随机选择,从而导致分页取出来的数据存在重复或者丢失的问题。然后解决的办法就是,加上order by Fmodify_time 就能满足,因为Fmodify_time 是一个索引,索引排序是有序的。后面测试小哥用相同的数据验证不丢数据,事情就告一段落了。

二、问题验证和解决篇
巧着,18年下半年花了2个月时间在学习mysql,在学习limit 分页查找的官方文档里有看到这么一段话:https://dev.mysql.com/doc/refman/5.7/en/limit-optimization.html
(1)如果在使用order by列中的多个行具有相同的值,则服务器可以按任何顺序自由返回这些行,并且可能根据整体执行计划的不同而不同。换句话说,这些行的排序顺序相对于无序列是不确定的。
(2)影响执行计划的一个因素是 LIMIT,因此ORDER BY 使用和不使用查询LIMIT可能会返回不同顺序的行。如果确保使用和不使用相同的行顺序很重要,请LIMIT在ORDER BY子句中包含其他列以使记录具有确定性,
比如使用主键。
那这么说,之前开发说通过order by 索引能使得结果返回有序就是有问题的了,带着这个疑问和官方文档的demo,结合实际的这个缺陷,做了个小验证。
(1)建表如下: 主键:id,索引:create_time
CREATE TABLE `test` (
`id` int() NOT NULL AUTO_INCREMENT,
`name` varchar() DEFAULT NULL,
`create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `index_time` (`create_time`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT= DEFAULT CHARSET=latin1
(2)插入数据如下:

(3)模拟分页查找,不使用order by。结果如下:

测试结果:丢数据id=1的记录,id=7的数据重复导致。看来这个问题是真的存在的。那按照开发哥哥的解决方法加上order by create_time就没问题了?
(4)模拟分页查找,使用order by + create_time(索引),如下:

测试结果:丢数据id=7的记录,id=3的数据重复导致。看来加了索引排序,还是有问题的,开发哥哥的“解决方法”实际是不能解决问题的。
(5)模拟分页查找,使用order by + create_time(索引)+ id(主键),如下:

测试结果:未丢数据。
分页查找limit M,N 的基本原理,就是服务器查找M+N条,然后返回从M条开始的后N条,导致上面问题出现的本质原因,还是mysql服务器查询后的数据排序不稳定导致。同小节的官方文档有说明当LIMIT row_count 和ORDER BY一起使用的时候,mysql不会对整个结果集排序,而是只对row_count 行的数据进行排序然后就返回。
If you combine LIMIT row_count with ORDER BY, MySQL stops sorting as soon as it has found the first row_count rows of the sorted result, rather than
sorting the entire result. If ordering is done by using an index, this is very fast. If a filesort must be done, all rows that match the query
without the LIMIT clause are selected, and most or all of them are sorted, before the firstrow_count are found. After the initial rows have been
found, MySQL does not sort any remainder of the result set.
三、未解之谜篇
大致的原理已经理清了,但是还有几个问题是没想明白的:
问题一: 如果构造测试数据的时,把id =1的记录,时间2018-12-12 05:01:00 修改为2018-12-12 04:59:00,不加order by 都不会出现丢数据和重复数据的问题:


这是为什么?数据的不同对结果是有影响的!!!那测试的时候数据要怎么构造呢?才能保证输入的数据都是有代表的呢?这个现象背后的原理是什么呢?
问题二:使用索引排序 order by+索引,是不稳定的?
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2019.01.28 问题跟进
问题二:使用索引排序 order by+索引,是不稳定的?
这个问题有“问题“,虽然order by 和 where 条件语句都是使用了index,但是并不确定所有的分页查找的sql都是使用索引排序。比如:

从第二个sql开始,执行计划就已经开始出现Using where; Using filesort ,跟之前第一条sql的执行计划是完全不一样的, 第二条sql之后就不能利用索引来避免排序,filesort并不意味着就是文件排序,其实也有可能是内存排序,这个主要由sort_buffer_size参数与结果集大小确定。MySQL内部实现排序主要有3种方式,常规排序,优化排序和优先队列排序,主要涉及3种排序算法:快速排序、归并排序和堆排序。
在学习其他文档了解到,在5.5版本中没有这个问题。产生这个现象的原因就是5.6针对limit M,N的语句采用了优先队列,而优先队列采用堆实现,使用的是堆排序算法,比如上述的例子order by create_time limit 4,2 limit 0,2 需要采用大小为2的大顶堆;limit 2,4需要采用大小为4的大顶堆。堆排序是非稳定的(对于相同的key值,无法保证排序后与排序前的位置一致),所以导致分页重复的现象。而解决这个问题的有效方法就是可以在排序中加上唯一值,比如主键id。
那为什么第一条执行sql的时候Extra = Using index condition,不需要访问表,直接拿数据呢? 后面sql执行全部都是Extra = Using where; Using filesort ,估计就是跟sort_buffer_size参数有关
学习参考文档:《数据库内核月报 - 2015 / 06》 链接:http://mysql.taobao.org/monthly/2015/06/04/
《MySQL排序原理》 https://blog.csdn.net/eagle89/article/details/81315981
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
优化order by 方法:
1 加大 max_length_for_sort_data 参数的设置
在 MySQL 中,决定使用老式排序算法还是改进版排序算法是通过参数 max_length_for_ sort_data 来决定的。当所有返回字段的最大长度小于这个参数值时,MySQL 就会选择改进后的排序算法,反之,则选择老式的算法。所以,如果有充足的内存让MySQL 存放须要返回的非排序字段,就可以加大这个参数的值来让 MySQL 选择使用改进版的排序算法。
2 去掉不必要的返回字段
当内存不是很充裕时,不能简单地通过强行加大上面的参数来强迫 MySQL 去使用改进版的排序算法,否则可能会造成 MySQL 不得不将数据分成很多段,然后进行排序,这样可能会得不偿失。此时就须要去掉不必要的返回字段,让返回结果长度适应 max_length_for_sort_data 参数的限制。
3 增大 sort_buffer_size 参数设置
这个值如果过小的话,再加上你一次返回的条数过多,那么很可能就会分很多次进行排序,然后最后将每次的排序结果再串联起来,这样就会更慢,增大 sort_buffer_size 并不是为了让 MySQL选择改进版的排序算法,而是为了让MySQL尽量减少在排序过程中对须要排序的数据进行分段,因为分段会造成 MySQL 不得不使用临时表来进行交换排序。
但是这个值不是越大越好:
1 Sort_Buffer_Size 是一个connection级参数,在每个connection第一次需要使用这个buffer的时候,一次性分配设置的内存。
2 Sort_Buffer_Size 并不是越大越好,由于是connection级的参数,过大的设置+高并发可能会耗尽系统内存资源。
3 据说Sort_Buffer_Size 超过2M的时候,就会使用mmap() 而不是 malloc() 来进行内存分配,导致效率降低。
缺陷的背后---LIMIT M,N 分页查找的更多相关文章
- LIMIT和OFFSET分页性能差!今天来介绍如何高性能分页
GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. GreatSQL是MySQL的国产分支版本,使用上与MySQL一致. 前言 之前的大多数人分页采用的都是这样: SELEC ...
- 如何优化Mysql千万级快速分页,limit优化快速分页,MySQL处理千万级数据查询的优化方案
如何优化Mysql千万级快速分页,limit优化快速分页,MySQL处理千万级数据查询的优化方案
- 【jsp 分页】mysql limit方式进行分页
项目结构示意图: splitPage |-com.balfish.bean Goods.java |-com.balfish.dao GoodsDao.java |-com.bal ...
- limit实现的分页查询
背景:原先是一次性查询加载到前段,表格插件自动分页,最近查询的数据量越来越大,长的时候需要等好几十秒,决定自己写一个后端分页,我写的和网上大神的略有不同,不是后端写一个类封装分页的参数,每次查询都是穿 ...
- mysql limit查询(分页查询)探究
MySQL的Limit子句 LIMIT offset,length Limit子句可以被用于强制 SELECT 语句返回指定的记录数.Limit接受一个或两个数字参数.参数必须是一个整数常量.如果给定 ...
- 多条件分页查找(SQL拼接方法)
def startTime=params.startTime+" 00:00:00" def endTime=params.endTime + " 23:59:59&q ...
- SQL极限函数limit()详解<分页必备>
limit含义: limit英语中的含义是限制,限定的意思.小日本曾上映过一个电影就是叫limit是由漫画改编的电影,剧情很变态,但不可否认小日本由于地狭人稠的原因,在观念上的资源危机意识还是很强的哈 ...
- 缺陷的背后(四)---多进程之for循环下fork子进程引发bug
导语 业务模块为实现高并发时的更快的处理速度,经常会采用多进程的方式去处理业务.多进程模式下常见的三种bug:for循环下fork子进程导致产生无数孙子进程,僵尸进程,接口窜包.本章主要介绍第一种常见 ...
- 缺陷的背后(三)---mysql之sql_mode为空的陷阱
导语 mysql服务器可以在不同的sql_mode模式下运行,并且可以根据sql_mode系统变量的值,为不同的客户机应用不同的模式.sql_mode会影响mysql支持的sql语法,并且会执行数据验 ...
随机推荐
- 基于335X平台的UBOOT中交换芯片驱动移植
基于335X平台的UBOOT中交换芯片驱动移植 一.软硬件平台资料 1.开发板:创龙AM3359核心板,网口采用RMII形式. 2.UBOOT版本:U-Boot-2016.05,采用FDT和DM. 3 ...
- leetcode刷题第二天<两数相加>
题目描述 给出两个 非空 的链表用来表示两个非负的整数.其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字. 如果,我们将这两个数相加起来,则会返回一个新的链表来表 ...
- ulimit -c unlimited的使用(转载)
ulimit -c unlimited ulimint -a 用来显示当前的各种用户进程限制Linux对于每个用户,系统限制其最大进程数,为提高性能,可以根据设备资源情况,设置个Linux用户的最大进 ...
- Docker操作笔记(一)使用镜像
使用镜像 一)获取镜像 从Docker镜像仓库获取命令的格式是: docker pull [选项] [Docker Registry 地址[:端口号]] 仓库名[:标签] 具体的选项可以通过docke ...
- WIN10 拨号连接下 如何开启移动热点
错误提示为:我们无法设置移动热点,因为你的电脑未建立以太网,WIFI或手机网络连接. 解决方法: 1. 首先用手机或其他设备建立无线热点. 2. 电脑连接步骤1中的热点,电脑端打开移动热点. 3. ...
- 使用gitflow流程完成功能时报错
报错 fatal: could not read Username for 'https://github.com': ······ 原因 使用https方式的时候 在https url 里面没有用户 ...
- [LeetCode] Implement Rand10() Using Rand7() 使用Rand7()来实现Rand10()
Given a function rand7 which generates a uniform random integer in the range 1 to 7, write a functio ...
- MGR
单主模式 参数修改 server_id=1 gtid_mode=ON enforce_gtid_consistency=ON binlog_checksum=NONE log_bin=binlog l ...
- mysql.user表详解
GRANT语法: GRANT 权限 ON 数据库.* TO 用户名@'登录主机' IDENTIFIED BY '密码' 权限: ALL,ALTER,CREATE,DROP,SELECT,U ...
- Ext选项卡tabpanel切换动态加载数据
鸣人不说暗话,来张图: 代码开始:(使用Ext,ajax加载数据,如果你们有好的方法也可以多多交流)var tabxsk = new Object(); //初始化 tabxsk.init = fun ...