MySQL 使用 order by limit 分頁排序會致使數據丟失和重複!

做爲程序員,常常寫 SQL 語句是正常不過了。然而,編寫一些 SQL 語句,總會出現一些奇怪的問題。程序員

問題

最近在項目中遇到一個很神奇的問題,MySQL 使用 order by 進行排序並進行分頁的時候,會出現部分數據丟失和重複。具體看下面這三張圖算法

圖一

圖二

圖三

其中,sql

  • 第一張圖查詢全部數據,並按 sort 字段排序,
  • 第二張圖,查詢從第 1 條數據開始,查詢 10 條數據,並按 sort 字段排序,
  • 第三張圖,查詢從第 11 條數據開始,查詢 10 條數據,並按 sort 字段排序,

仔細看我用紅色標記出來的,能夠發現,分類11 的數據在分頁後查詢不出來,而分類18 則出現了兩次。很明顯的發現,當進行數據分頁時,部分數據出現了丟失和重複。數據庫

分析緣由

在 MySQL 關係型數據庫中,每每會存在多種排序算法。經過 MySQL 的源碼和官方文檔介紹能夠得知,它的排序規律能夠總結以下:ide

  1. 當 order by 不使用索引進行排序時,將使用排序算法進行排序;
  2. 若排序內容能所有放入內存,則僅在內存中使用快速排序;
  3. 若排序內容不能所有放入內存,則分批次將排好序的內容放入文件,而後將多個文件進行歸併排序;
  4. 若排序中包含 limit 語句,則使用堆排序優化排序過程。

根據上面的總結,當 order by limit 分頁出現數據丟失和重複。而 order by 的 sort 字段沒有使用索引(正常狀況下,排序的字段也不會使用索引),若是使用了索引,則會進行索引排序。優化

所以能夠得出,上面的圖二和圖三的 SQL 語句使用了堆排序。由於 sort 字段沒有索引,因此沒走索引排序;而且使用了 limit。致使最終使用了堆排序。.net

若是瞭解算法的你,應該知道堆排序是不穩定的。這種不穩定性,指的就是屢次排序後,各個數的相對位置發生了變化。code

可是,不是全部的 MySQL 版本都是這樣。從 MySQL 5.6 版本開始,優化器在使用 order by limit 時,作了上面的優化,致使排序字段沒有使用索引時,使用堆排序。排序

問題解決

經過上面的分析,有兩種解決方案能夠解決此問題。索引

  1. 方案一:下降 MySQL 版本爲 5.5 或更低版本。此方案不推薦,數據庫版本通常是指定的,下降數據庫版本工做量較大。
  2. 方案二:在 order by 排序字段裏,添加有索引的字段,好比主鍵ID。這樣在排序時能夠保證順序穩定。

在圖2、圖三中,增長主鍵 category_id 字段排序後,就不會出現數據丟失和重複了。

image-20210117221819803

image-20210117221759168

總結

若是查詢數據進行排序和分頁時,若是排序字段沒有使用索引,必定要添加一個有索引的字段,好比主鍵 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 排序分頁數據重複問題

相關文章
相關標籤/搜索