1 问题背景

线上有一个批处理任务,会批量读取昨日的数据,经过一系列加工后,插入到今日的表中。表结构如下:

 1 CREATE TABLE `detail_yyyyMMdd` (
2 `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
3 `batch_no` varchar(64) NOT NULL COMMENT '批次号',
4 `order_id` varchar(64) NOT NULL COMMENT '订单ID',
5 `user_id` varchar(64) NOT NULL COMMENT '用户ID',
6 `status` varchar(4) NOT NULL COMMENT '状态',
7 `product_id` varchar(32) NOT NULL COMMENT '产品ID',
8 PRIMARY KEY (`id`),
9 KEY `idx_batchno_userid` (`batch_no`,`user_id`)
10 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='明细表';

因数据量较大,批量读取昨日数据时,使用了分页查询limit语句,查询sql如下:

SELECT id,batch_no,order_id,user_id,status,product_id FROM detail_yyyyMMdd WHERE batch_no=‘batch_type_yyyyMMdd’ LIMIT ?,?;

从某一天开始,客服频繁收到客诉,反馈数据未更新。

2 问题排查

初步排查,客诉反馈的数据确实没有插入。线上增加了一些业务日志后,发现分页查询昨日数据时,第二次查询的结果集与第一次查询的结果集有重复数据。

与DBA沟通,确认了客诉爆发前一天做了一次数据库变更,兄弟团队为了解决一个慢查询问题,增加了一个索引,变更sql如下:

ALTER TABLE `detail_yyyyMMdd` ADD INDEX `idx_batchno_status_productid` (`batch_no`, `status`, `product_id`);

DBA分析,该变更sql上线前,分页查询会走idx_batchno_userid索引,上线后则走idx_batchno_status_productid,而该索引存在大量重复记录,导致每次分页查询的数据都可能和之前的重复。

为什么索引有重复记录时,分页查询的数据就可能与之前的重复呢?在网上搜了下,这里贴一篇官方的文档:https://dev.mysql.com/doc/refman/5.6/en/limit-optimization.html

If multiple rows have identical values in the ORDER BY columns, the server is free to return those rows in any order, and may do so differently depending on the overall execution plan. In other words, the sort order of those rows is nondeterministic with respect to the nonordered columns.

One factor that affects the execution plan is LIMIT, so an ORDER BY query with and without LIMIT may return rows in different orders.

If it is important to ensure the same row order with and without LIMIT, include additional columns in the ORDER BY clause to make the order deterministic.

在MySQL 5.6的版本上,优化器在遇到order by limit语句时,做了个查询优化,即使用了priority queue(优先级队列)。采用priority queue能够根据limit N维护一个大小为N的堆,在排序的过程中,只用保留N条记录即可。但堆排序是一个不稳定的排序算法,所以当排序字段值存在重复时,返回的数据顺序可能会不一样,其中一个影响因素就是limit。

解决方案:查询语句加上order by id(保证排序字段的唯一性),上线后,问题得到解决。

但仍然存在两个疑点:

  • 原来的索引idx_batchno_userid也会有重复记录,为什么一直没有爆出问题?

  • 线上的查询语句只有limit,没有order by,可以直接按照索引的有序性(索引聚簇表)进行读取并分页,不需要触发priority queue优化。

3 问题定位

仔细观察了两次分页查询的结果集后发现,两次结果集的数据顺序分别对应两个索引,即第一次分页查询走了索引idx_batchno_userid,第二次分页查询走了索引idx_batchno_status_productid,两个索引的数据顺序是不一样的,从而导致两次分页查询的数据存在重复。与DBA交流,MySQL的优化器会基于成本选择最优的索引,而这两个索引的成本相差不大。

这个结论在线下得到复现,但并不能稳定复现。在总结果集没有变化的情况下,两次分页查询分别走了不同索引的根因,还有待继续深挖。

End

MySQL使用limit进行分页查询时,可能会出现重复数据,可以通过加上order by子句并保证排序字段的唯一性来解决。

MySQL分页查询limit踩坑记的更多相关文章

  1. mysql分页查询-limit

    分页查询的sql: select * from table limit 4,10; 4表示查询的索引,索引是从0开始,4表示从第五条数据开始查询,10表示要查询多少条数据,10表示查询十条数据 如果从 ...

  2. Spark踩坑记——数据库(Hbase+Mysql)

    [TOC] 前言 在使用Spark Streaming的过程中对于计算产生结果的进行持久化时,我们往往需要操作数据库,去统计或者改变一些值.最近一个实时消费者处理任务,在使用spark streami ...

  3. [转]Spark 踩坑记:数据库(Hbase+Mysql)

    https://cloud.tencent.com/developer/article/1004820 Spark 踩坑记:数据库(Hbase+Mysql) 前言 在使用Spark Streaming ...

  4. Spark踩坑记——数据库(Hbase+Mysql)转

    转自:http://www.cnblogs.com/xlturing/p/spark.html 前言 在使用Spark Streaming的过程中对于计算产生结果的进行持久化时,我们往往需要操作数据库 ...

  5. Spark踩坑记——共享变量

    [TOC] 前言 Spark踩坑记--初试 Spark踩坑记--数据库(Hbase+Mysql) Spark踩坑记--Spark Streaming+kafka应用及调优 在前面总结的几篇spark踩 ...

  6. Spark踩坑记——Spark Streaming+Kafka

    [TOC] 前言 在WeTest舆情项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端,我们利用了spark strea ...

  7. Spark踩坑记——从RDD看集群调度

    [TOC] 前言 在Spark的使用中,性能的调优配置过程中,查阅了很多资料,之前自己总结过两篇小博文Spark踩坑记--初试和Spark踩坑记--数据库(Hbase+Mysql),第一篇概况的归纳了 ...

  8. 记一次 Spring 事务配置踩坑记

    记一次 Spring 事务配置踩坑记 问题描述:(SpringBoot + MyBatisPlus) 业务逻辑伪代码如下.理论上,插入数据 t1 后,xxService.getXxx() 方法的查询条 ...

  9. Spark踩坑记:Spark Streaming+kafka应用及调优

    前言 在WeTest舆情项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端,我们利用了spark streaming从k ...

随机推荐

  1. [源码解析] 并行分布式任务队列 Celery 之 EventDispatcher & Event 组件

    [源码解析] 并行分布式任务队列 Celery 之 EventDispatcher & Event 组件 目录 [源码解析] 并行分布式任务队列 Celery 之 EventDispatche ...

  2. window下批量删除指定后缀文件

    例子: 批量删除当前路径下后缀为 .jpg和 .json del /a /f /s /q "*.jpg" "*.json" *为通配符/a /f 是强制删除所有 ...

  3. 面试遇到的坑JS深拷贝和浅拷贝

    首先要搞明白深拷贝和钱拷贝的区别要先搞明白 栈和堆的区别 一.栈 栈存储基础数据类型,如: String.Number.Boolean.Null.Underined,这些简单的基础数据类型能够直接存储 ...

  4. CRM帮助初创企业降本增效的四个方法

    对大部分初创公司来说,只有少数企业能够实现盈利,大部分只能维持盈亏平衡甚至是亏损.这是因为初创企业很难在短时间之内找到稳定的赢利点,而企业面临的风险和投入又是无法预知的.初创企业想要快速盈利,只能降低 ...

  5. 《Spring 手撸专栏》第 3 章:初显身手,运用设计模式,实现 Bean 的定义、注册、获取

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你是否能预见复杂内容的设计问题? 讲道理,无论产品功能是否复杂,都有很大一部分程序员 ...

  6. ElasticSearch实战系列十一: ElasticSearch错误问题解决方案

    前言 本文主要介绍ElasticSearch在使用过程中出现的各种问题解决思路和办法. ElasticSearch环境安装问题 1,max virtual memory areas vm.max_ma ...

  7. 对spring创建对象时为何要使用接口

    对spring创建对象时为何要使用接口,而使用接口的实现类会报错 接上一篇问题的解答:Spring AOP获取不了增强类(额外方法)和无法通过getBean()获取对象 此问题发生在动态代理时,比如对 ...

  8. goland无法解析go mod问题解决

    Goland 版本是 2018.03 由于之前使用GOPATH方式进行存在很多项目,在怎么配置后都无法解析包,但是命令行时正常的. 这边设置全局的设置,开启goland的go mod包管理. 创建新项 ...

  9. [bug] Hive:map.xml could only be replicated to 0 nodes instead of minReplication (=1). There are 0 datanode(s) running and no node(s) are excluded in this operation.

    原因: datanode未运行,重启hdfs

  10. Linux_控制服务与守护进程

    一.systemd 1.systemd简介 1️⃣:systemd是用户空间的第一个应用程序,即/sbin/init 2️⃣:init程序的类型: SysV风格:init(centos5),实现系统初 ...