做为程序员,常常写 SQL 语句是正常不过了。然而,编写一些 SQL 语句,总会出现一些奇怪的问题。程序员
最近在项目中遇到一个很神奇的问题,MySQL 使用 order by 进行排序并进行分页的时候,会出现部分数据丢失和重复。具体看下面这三张图算法
其中,sql
仔细看我用红色标记出来的,能够发现,分类11 的数据在分页后查询不出来,而分类18 则出现了两次。很明显的发现,当进行数据分页时,部分数据出现了丢失和重复。数据库
在 MySQL 关系型数据库中,每每会存在多种排序算法。经过 MySQL 的源码和官方文档介绍能够得知,它的排序规律能够总结以下:ide
根据上面的总结,当 order by limit 分页出现数据丢失和重复。而 order by 的 sort 字段没有使用索引(正常状况下,排序的字段也不会使用索引),若是使用了索引,则会进行索引排序。优化
所以能够得出,上面的图二和图三的 SQL 语句使用了堆排序。由于 sort 字段没有索引,因此没走索引排序;而且使用了 limit。致使最终使用了堆排序。.net
若是了解算法的你,应该知道堆排序是不稳定的。这种不稳定性,指的就是屡次排序后,各个数的相对位置发生了变化。code
可是,不是全部的 MySQL 版本都是这样。从 MySQL 5.6 版本开始,优化器在使用 order by limit 时,作了上面的优化,致使排序字段没有使用索引时,使用堆排序。排序
经过上面的分析,有两种解决方案能够解决此问题。索引
在图2、图三中,增长主键 category_id 字段排序后,就不会出现数据丢失和重复了。
若是查询数据进行排序和分页时,若是排序字段没有使用索引,必定要添加一个有索引的字段,好比主键 ID,保证顺序稳定。不然,查询的数据会致使数据丢失和重复。
理解此问题出现的缘由后,赶忙去看看你的项目中有没有这种状况吧!要否则出问题就很差办了!
最后,附上新建表和表相关数据的 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);