【走进Mysql之消失的数据】order by + limit 数据重复 + 消失!

前言

先说下背景,最近在做业务需求 ,而这个需求会插入很多数据 ,并且这个数据拥有过期时间 ,需要在前端根据过期时间降序分页展示的~

再来描述下问题: 我在测试过程中发现我明明新插入的一条数据,在列表页根本没展示出来!并且在下一页中我看到了上一页已经出现过的数据

带着问题,我们先根据下面的SQL来简单复现下问题~


问题案例

我们先来创建表和数据

sql 复制代码
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT NULL,
  `add_time` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of student
-- ----------------------------
BEGIN;
INSERT INTO `student` (`id`, `name`, `add_time`) VALUES (1, '熊大', '2023-11-14 22:03:44');
INSERT INTO `student` (`id`, `name`, `add_time`) VALUES (2, '皮皮虾', '2023-11-14 22:03:44');
INSERT INTO `student` (`id`, `name`, `add_time`) VALUES (3, '熊二', '2023-11-14 22:03:44');
INSERT INTO `student` (`id`, `name`, `add_time`) VALUES (4, '翠花', '2023-11-13 02:03:52');
INSERT INTO `student` (`id`, `name`, `add_time`) VALUES (5, '光头强', '2023-11-13 02:03:52');
INSERT INTO `student` (`id`, `name`, `add_time`) VALUES (6, '吉吉国王', '2023-11-13 02:03:52');
INSERT INTO `student` (`id`, `name`, `add_time`) VALUES (7, '喜羊羊', '2023-11-12 16:11:00');
INSERT INTO `student` (`id`, `name`, `add_time`) VALUES (8, '沸羊羊', '2023-11-14 22:03:44');
INSERT INTO `student` (`id`, `name`, `add_time`) VALUES (9, '美羊羊', '2023-11-14 22:03:44');
INSERT INTO `student` (`id`, `name`, `add_time`) VALUES (10, '懒洋洋', '2023-11-11 05:03:52');
INSERT INTO `student` (`id`, `name`, `add_time`) VALUES (11, '炭治郎', '2023-11-13 02:03:52');
INSERT INTO `student` (`id`, `name`, `add_time`) VALUES (12, '五条悟', '2023-11-13 02:03:52');
INSERT INTO `student` (`id`, `name`, `add_time`) VALUES (13, '小鸟游六花', '2023-11-13 02:03:52');
INSERT INTO `student` (`id`, `name`, `add_time`) VALUES (14, '勇太', '2023-11-13 02:03:52');
INSERT INTO `student` (`id`, `name`, `add_time`) VALUES (15, 'The shy', '2023-11-14 22:03:44');
INSERT INTO `student` (`id`, `name`, `add_time`) VALUES (16, '虎杖悠仁', '2023-11-14 22:03:44');
INSERT INTO `student` (`id`, `name`, `add_time`) VALUES (17, '猪猪', '2023-11-14 22:03:44');
INSERT INTO `student` (`id`, `name`, `add_time`) VALUES (18, '小明', '2023-11-14 22:03:44');
INSERT INTO `student` (`id`, `name`, `add_time`) VALUES (19, '小刚', '2023-11-14 22:03:44');
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

执行SQl后数据如上 ,我们假设现在需要根据过期时间降序并且分页每页4条进行展示 ,那么第1、2页SQL如下:

sql 复制代码
-- 第一页
SELECT * from student ORDER BY add_time desc LIMIT 0, 4;

-- 第二页
SELECT * from student ORDER BY add_time desc LIMIT 4, 4;

我们分别执行这两条语句,结果如下:

我们发现熊大这条数据,在第一页和第二页中都出现了!

又例如下面这条SQl,我们每页6条,查询第一、二页,一共根据add_time降序查询前12条数据 ,但根据我下面的对比图发现,虎杖悠仁这条数据在第一二页中都没出现???


原因

上面自己定义了一下数据,简单复现了我遇到的问题~

那么接下来该查找原因了,sql语法啥的感觉也没啥问题,为啥会出现重复数据、丢数据的问题呢,奇奇怪怪的~

网上搜了一大堆,大致了解了是什么情况,那么我也来简单总结下吧

首先,参考Mysql官方文档的下面这两句话

  • If an index is not used for ORDER BY but a LIMIT clause is also present, the optimizer may be able to avoid using a merge file and sort the rows in memory using an in-memory filesort operation.

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.

如官方文档所说,结合我给出的测试数据,当多行具有相同值时,mysql可以以任意顺序返回,这样一来数据就可能重复出现甚至丢失,也符合我们遇到的问题。

并且文档还说如果order by的字段没有索引,并且存在limit语句时,优化器会使用filesort来进行排序

我们通过explain查看执行计划,确实如官方所说,使用到了filesort,代表需要排序

MySQL会有内存排序和外部排序两种:

  • 如果待排序的数据量小于sort buffer size,排序就在内存中完成(快速排序);
  • 如果待排序的数据量大于sort buffer size,就使用临时文件进行外部排序(归并排序);

sort buffer size默认256k

无论是快排还是归并,都是根据全部结果进行排序后,再根据limit取数,这是不会影响数据结果的(重复数据、丢失数据)

但是,MySQL 5.6 版本针对 ORDER BY LIMIT做了个小优化(排序字段无索引,且列值不唯一时):优化器在遇到 ORDER BY LIMIT语句的时候 ,使用了priority queue(优先队列)

关键就在于这个优先队列,并不是一下子对全部结果排序,然后取出需要的

结合order by add_time desc limit 0, 5来说,是会维护一个大小为5的大顶堆,当出现大于等于堆顶的数据时,该数据会加入到堆中,同时堆需要进行排序调整,淘汰堆中最小的那个数据

大顶堆 :每个结点的值都大于或等于其左右孩子结点的值 小顶堆:每个结点的值都小于或等于其左右孩子结点的值

add_time都一样时,淘汰的数据就不可控了,因为在第一二页我们可能就会看到重复的数据,甚至丢失数据


解决

除了使用add_time外,我们在增加使用主键id来进行降序,这样一来就能保证唯一性和有序性,从而避免出现重复数据和丢失数据的情况。


总结

通过这次遇到的问题,也了解到了Mysql一些新知识点,还是很不错滴~

正所谓干的越多,错的越多,hh,但最后学到的也更多!!!

我是 Code皮皮虾 ,会在以后的日子里跟大家一起学习,一起进步! 觉得文章不错的话,可以在 掘金 关注我,这样就不会错过很多技术干货啦~


参考

相关推荐
程序员皮皮林4 分钟前
Java jar 如何防止被反编译?代码写的太烂,害怕被人发现
java·开发语言·jar
neo_Ggx239 分钟前
MySQL数据库备份攻略:从Docker到本地部署
数据库·mysql·docker
听风同学35 分钟前
RAG的灵魂-向量数据库技术深度解析
后端·架构
橙序员小站39 分钟前
搞定系统面试题:如何实现分布式Session管理
java·后端·面试
老青蛙1 小时前
权限系统设计-功能设计
后端
粘豆煮包1 小时前
脑抽研究生Go并发-1-基本并发原语-下-Cond、Once、Map、Pool、Context
后端·go
叫我阿柒啊1 小时前
从Java全栈到Vue3实战:一次真实面试中的技术探索
java·数据库·spring boot·微服务·typescript·vue3·restful
武子康1 小时前
Java-118 深入浅出 MySQL ShardingSphere 分片剖析:SQL 支持范围、限制与优化实践
java·大数据·数据库·分布式·sql·mysql·性能优化
IT_陈寒1 小时前
Vite5.0性能翻倍秘籍:7个极致优化技巧让你的开发体验飞起来!
前端·人工智能·后端
Edward.W2 小时前
用 Go + HTML 实现 OpenHarmony 投屏(hdckit-go + WebSocket + Canvas 实战)
开发语言·后端·golang