一、问题发现篇

最近组内做了一次典型缺陷分享时,翻阅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. 我的 Putty 配色方案

    首先,右键单击 Putty 顶部边框,在弹出菜单中选择 Change settings,进入颜色设置 Category->Window->Colours 然后,按以下参数配置进行修改: D ...

  2. 第二篇flask响应方式

    响应三件套 1.Flask中的HTTPResponse @app.route('/home') # app中的route装饰器 def home(): # 视图 return '登陆成功' #HTTP ...

  3. World Finals 2018 感想

    垃圾比赛卡我读入卡我输出毁我青春.

  4. The Water Bowls [POJ3185] [开关问题]

    题意 一串长度为20的0,1数列,每次翻转i,会影响i-1,i+1,也被翻转,最少翻转成0的步骤数是多少? Sample Input 0 0 1 1 1 0 0 1 1 0 1 1 0 0 0 0 0 ...

  5. Linux内核笔记:epoll实现原理(一)

    一.说明 针对的内核版本为4.4.10. 本文只是我自己看源码的简单笔记,如果想了解epoll的实现,强烈推荐下面的文章: The Implementation of epoll(1) The Imp ...

  6. __x__(17)0906第三天__块元素block_内联元素inline_行内块元素inline-block

    1. 块元素block 独占一行的元素 一般使用块元素包含内联元素,用作页面布局 <a> 标签可以包含任何除了a标签以外的元素 <p> 标签不能包含块元素 h1... ...h ...

  7. 2018-2019-1 20189210 《Linux内核原理与分析》第三周作业

    一.学习笔记 计算机的"三大法宝" 1.程序存储计算机. 2.函数调用对栈.堆栈的作用是:记录函数调用框架.传递函数参数.保存返回值地址.提供函数内部局部变量的存储空间.函数调用堆 ...

  8. CentOS裸机环境下安装php-7.3.1

    安装步骤如下 安装必要的软件 获取源码 编译安装 安装过程可能遇到的一些问题 编译参数详解 安装步骤如下 安装必要的软件 yum install -y autoconf automake libtoo ...

  9. laravel整合workerman做聊天室

    测试工具  http://www.blue-zero.com/WebSocket/ 2018年8月6日17:28:24 <?php namespace App\Console\Commands; ...

  10. Multi-Projector Based Display Code ---- Calibration

    Overview As mentioned previously, there are two main steps in generating a seamless display. The fir ...