MySQL 使用 order by limit 分页排序会导致数据丢失和重复!

作为程序员,经常写 SQL 语句是正常不过了。然而,编写一些 SQL 语句,总会出现一些奇怪的问题。

问题

最近在项目中遇到一个很神奇的问题,MySQL 使用 order by 进行排序并进行分页的时候,会出现部分数据丢失和重复。具体看下面这三张图

MySQL 使用 order by limit 分页排序会导致数据丢失和重复!

MySQL 使用 order by limit 分页排序会导致数据丢失和重复!

MySQL 使用 order by limit 分页排序会导致数据丢失和重复!

其中,

  • 第一张图查询所有数据,并按 sort 字段排序,
  • 第二张图,查询从第 1 条数据开始,查询 10 条数据,并按 sort 字段排序,
  • 第三张图,查询从第 11 条数据开始,查询 10 条数据,并按 sort 字段排序,

仔细看我用红色标记出来的,可以发现,分类11 的数据在分页后查询不出来,而分类18 则出现了两次。很明显的发现,当进行数据分页时,部分数据出现了丢失和重复。

分析原因

在 MySQL 关系型数据库中,往往会存在多种排序算法。通过 MySQL 的源码和官方文档介绍可以得知,它的排序规律可以总结如下:

  1. 当 order by 不使用索引进行排序时,将使用排序算法进行排序;
  2. 若排序内容能全部放入内存,则仅在内存中使用快速排序;
  3. 若排序内容不能全部放入内存,则分批次将排好序的内容放入文件,然后将多个文件进行归并排序;
  4. 若排序中包含 limit 语句,则使用堆排序优化排序过程。

根据上面的总结,当 order by limit 分页出现数据丢失和重复。而 order by 的 sort 字段没有使用索引(正常情况下,排序的字段也不会使用索引),如果使用了索引,则会进行索引排序。

因此可以得出,上面的图二和图三的 SQL 语句使用了堆排序。因为 sort 字段没有索引,所以没走索引排序;并且使用了 limit。导致最终使用了堆排序。

如果了解算法的你,应该知道堆排序是不稳定的。这种不稳定性,指的就是多次排序后,各个数的相对位置发生了变化。

但是,不是所有的 MySQL 版本都是这样。从 MySQL 5.6 版本开始,优化器在使用 order by limit 时,做了上面的优化,导致排序字段没有使用索引时,使用堆排序。

问题解决

通过上面的分析,有两种解决方案可以解决此问题。

  1. 方案一:降低 MySQL 版本为 5.5 或更低版本。此方案不推荐,数据库版本一般是指定的,降低数据库版本工作量较大。
  2. 方案二:在 order by 排序字段里,添加有索引的字段,比如主键ID。这样在排序时可以保证顺序稳定。

在图二、图三中,增加主键 category_id 字段排序后,就不会出现数据丢失和重复了。

MySQL 使用 order by limit 分页排序会导致数据丢失和重复!

MySQL 使用 order by limit 分页排序会导致数据丢失和重复!

总结

如果查询数据进行排序和分页时,如果排序字段没有使用索引,一定要添加一个有索引的字段,比如主键 ID,保证顺序稳定。否则,查询的数据会导致数据丢失和重复。

理解此问题出现的原因后,赶紧去看看你的项目中有没有这种情况吧!要不然出问题就不好办了!

相关 SQL 语句

最后,附上新建表和表相关数据的 SQL 语句:

DROP TABLE IF EXISTS `sys_category`;
CREATE TABLE `sys_category` (
  `category_id` bigint NOT NULL AUTO_INCREMENT,
  `category_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT ‘分类名称‘,
  `sort` int DEFAULT NULL COMMENT ‘分类排序‘,
  PRIMARY KEY (`category_id`)
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8;

INSERT INTO `sys_category` VALUES (1,‘分类1‘,1),(2,‘分类2‘,2),(3,‘分类3‘,20),(4,‘分类4‘,21),(5,‘分类5‘,22),(6,‘分类6‘,23),(7,‘分类7‘,0),(8,‘分类8‘,0),(9,‘分类9‘,0),(10,‘分类10‘,0),(11,‘分类11‘,0),(12,‘分类12‘,0),(13,‘分类13‘,0),(14,‘分类14‘,0),(15,‘分类15‘,0),(16,‘分类16‘,0),(17,‘分类17‘,0),(18,‘分类18‘,0);

参考文章

从根上理解order by limit分页数据重复问题

MySQL 5.6之后 order by limit 排序分页数据重复问题

MySQL 使用 order by limit 分页排序会导致数据丢失和重复!

上一篇:MySQL的sql_mode模式说明及设置


下一篇:Mysql13—— TCL语言