一、问题发现篇

最近组内做了一次典型缺陷分享时,翻阅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 分页查找的更多相关文章

  1. LIMIT和OFFSET分页性能差!今天来介绍如何高性能分页

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. GreatSQL是MySQL的国产分支版本,使用上与MySQL一致. 前言 之前的大多数人分页采用的都是这样: SELEC ...

  2. 如何优化Mysql千万级快速分页,limit优化快速分页,MySQL处理千万级数据查询的优化方案

    如何优化Mysql千万级快速分页,limit优化快速分页,MySQL处理千万级数据查询的优化方案

  3. 【jsp 分页】mysql limit方式进行分页

    项目结构示意图: splitPage |-com.balfish.bean     Goods.java |-com.balfish.dao       GoodsDao.java |-com.bal ...

  4. limit实现的分页查询

    背景:原先是一次性查询加载到前段,表格插件自动分页,最近查询的数据量越来越大,长的时候需要等好几十秒,决定自己写一个后端分页,我写的和网上大神的略有不同,不是后端写一个类封装分页的参数,每次查询都是穿 ...

  5. mysql limit查询(分页查询)探究

    MySQL的Limit子句 LIMIT offset,length Limit子句可以被用于强制 SELECT 语句返回指定的记录数.Limit接受一个或两个数字参数.参数必须是一个整数常量.如果给定 ...

  6. 多条件分页查找(SQL拼接方法)

    def startTime=params.startTime+" 00:00:00"  def endTime=params.endTime + " 23:59:59&q ...

  7. SQL极限函数limit()详解<分页必备>

    limit含义: limit英语中的含义是限制,限定的意思.小日本曾上映过一个电影就是叫limit是由漫画改编的电影,剧情很变态,但不可否认小日本由于地狭人稠的原因,在观念上的资源危机意识还是很强的哈 ...

  8. 缺陷的背后(四)---多进程之for循环下fork子进程引发bug

    导语 业务模块为实现高并发时的更快的处理速度,经常会采用多进程的方式去处理业务.多进程模式下常见的三种bug:for循环下fork子进程导致产生无数孙子进程,僵尸进程,接口窜包.本章主要介绍第一种常见 ...

  9. 缺陷的背后(三)---mysql之sql_mode为空的陷阱

    导语 mysql服务器可以在不同的sql_mode模式下运行,并且可以根据sql_mode系统变量的值,为不同的客户机应用不同的模式.sql_mode会影响mysql支持的sql语法,并且会执行数据验 ...

随机推荐

  1. JS对象与原型链

    每个函数都存在一个prototype的属性,然后这个属性值为一个对象,我们称之为原型对象 每个对象都存在着一个隐藏的属性"__proto__" 这个属性引用了创建这个对象的函数的p ...

  2. [C#] 動的にアセンブリをロードする

    アプリケーション ドメインにおいて起動時に読み込まれない別のアセンブリ (.dll や .exe) を読み込む場合.System.Reflection.Assemby クラスの Load メソッドを使 ...

  3. C# 集合已修改 可能无法执行枚举操作 zz

    今天编写程序时 修改了list集合 在foreach时报 “集合已修改:可能无法执行枚举操作.”错误. 首先想到的是没有锁定集合对象. 增加了 private readonly object sync ...

  4. 一道dfs和dp结合的好题 --- Longest Run on a SnowboardUVA-10285

    题目链接: https://vjudge.net/problem/19213/origin 大致题意: 一个滑雪者想知道自己在固定高度的山坡中最多能滑的距离是多少. 思路: 首先想到的就是dfs,但是 ...

  5. vue入门1(搭建项目)

    安装node.js 安装cnpm npm install -g cnpm --registry=http://registry.npm.taobao.org 安装vue-cli脚手架构建工具 npm ...

  6. Object 中 equals()使用

    详细看: https://www.cnblogs.com/naihuangbao/p/9445027.html 1. ==是判断两个变量或实例是不是指向同一个内存空间equals是判断两个变量或实例所 ...

  7. 15树莓派安装图形界面截图工具Shutter

    2017-09-27 13:02:54 参数-添加/删除软件-搜索软件名称“shutter”,勾选,点击“应用”,等待安装完成后点击“确定”.(需下载安装包后安装) 补充:更多截图软件可以https: ...

  8. 编程菜鸟的日记-初学尝试编程-C++ Primer Plus 第5章编程练习6

    #include <iostream>#include <string>using namespace std;struct car{ string pro; int  yea ...

  9. PeopleSoft如何查找jar包冲突

    PeopleSoft要查找jar包冲突问题,不像maven可以打印出所有依赖,但既然是在JVM上运行,就可以启用JVM参数 路经:%ps_cfg_home%\appserv\Domain 文件名:ps ...

  10. Angularjs判断页面是否已经渲染结束(动态给标签长度)

    相信大家都会碰到这样的问题.页面循环li.但是因为个数不知道.没有办法给li设置固定宽度.那么这时就需要动态计算数据长度并动态改变li的宽度 <!--周边信息--> <div cla ...