Mysql 报表下载为什么出现了重复的数据?
某天,产品同学反馈了个生产问题:订单报表的下载功能,下载的数据里面少部分数据而且出现部分重复订单。纳尼?这么简单的一个单表数据下载,而且是历史悠久的老功能也能出现 bug。按开发惯例,首先怀疑产品同学使用下载功能的姿势不对。。。。。当然,这是玩笑。按产品同学提供的账号和条件重试了下载,果然 bug 复现,本着有问题解决问题的目的开始排查问题根源(数据安全考虑,举例使用表和数据都是模拟的)。
第一步,先从罪魁祸首的 SQL 分析
SELECT
`ordercode`,
`productcode`,
`num`
FROM
`t_order` WHERE productcode LIKE '10%'
LIMIT xnum, ynum ; 乍一看,一个很简单的 sql 语句,也不存在什么明显的问题。表结构如下:
试过几个 limit 参数,该 sql 的执行计划也是正常的,也都走了索引,难道索引本身有问题?
接着分析了几个有问题的下载文件,发现问题数据都是集中在文件的后面,难道是深度查询导致的问题?加大 limit 的取数位,重新看执行计划,果然问题出现了!
第二步,分析为什么出现这样的问题?
相信不少老司机已经知道问题的根源了,没错就是索引导致的!mysql 索引(innodb)的数据结构是个 B+树,叶子节点存放的键值是个有序的双向链表。sql 分页取前面的数据时,查询条件也是满足 index_2 的索引的,mysql 也忠实的执行了这个索引去取数据,当然取出的数据也是按索引字段 ordercode 排序的。虽然 sql 有潜在问题,但是数据量少的时候妥妥的不会出现任何问题,这也是为什么这个老功能一直正常的原因。
根据墨菲定律,会出错的事情一定会出错!当下载数据量很大的时候,就像上面有几十万的量,问题就出现了。原因就是 Mysql 的 limit x y 并不是像不少人当然的认为那样,像游标一样直接定位到第 x 条上,实际情况是要获取第 201000 到 202000 这 1000 条数据的时候会取出 202000 条数据的结果集再舍弃前面的 201000 条!这么大的结果集已经足够影响 mysql 的索引选择的策略,结果集占比整表数据量的比例过大,直接后果是导致索引失效,mysql 直接利用主键扫描全表取数据,而主键的顺序是以 id 这个字段来的,前后查询排序字段完全不一样,当然会存在取错数据行了!
第三步,解决问题
1. 方法 1:分页查询,前后执行索引不一致导致的,force index 强制使用 index_2 也可以解决,当然不推荐这么改,容易埋下隐患。
2. 方法 2:既然已经分析了最终根源是排序字段不一致导致的,sql 里面加个 order by 就可以解决问题
到此问题看似已经解决了,但是真的完美了吗???隐藏的彩蛋有待细心人发现。。。。
版权声明: 本文为 InfoQ 作者【三石】的原创文章。
原文链接:【http://xie.infoq.cn/article/497d52374ccc8c9bda9c6e4ef】。文章转载请联系作者。
评论