MySQL分頁查詢limit踩坑記


1 問題背景

線上有一個批處理任務,會批量讀取昨日的數據,經過一系列加工后,插入到今日的表中。表結構如下:

 1 CREATE TABLE `detail_yyyyMMdd` (
 2   `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
 3   `batch_no` varchar(64) NOT NULL COMMENT '批次號',
 4   `order_id` varchar(64) NOT NULL COMMENT '訂單ID',
 5   `user_id` varchar(64) NOT NULL COMMENT '用戶ID',
 6   `status` varchar(4) NOT NULL COMMENT '狀態',
 7   `product_id` varchar(32) NOT NULL COMMENT '產品ID',
 8   PRIMARY KEY (`id`),
 9   KEY `idx_batchno_userid` (`batch_no`,`user_id`)
10 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='明細表';

因數據量較大,批量讀取昨日數據時,使用了分頁查詢limit語句,查詢sql如下:

SELECT id,batch_no,order_id,user_id,status,product_id FROM detail_yyyyMMdd WHERE batch_no=‘batch_type_yyyyMMdd’ LIMIT ?,?;

從某一天開始,客服頻繁收到客訴,反饋數據未更新。

2 問題排查

初步排查,客訴反饋的數據確實沒有插入。線上增加了一些業務日志后,發現分頁查詢昨日數據時,第二次查詢的結果集與第一次查詢的結果集有重復數據。

與DBA溝通,確認了客訴爆發前一天做了一次數據庫變更,兄弟團隊為了解決一個慢查詢問題,增加了一個索引,變更sql如下:

ALTER TABLE `detail_yyyyMMdd` ADD INDEX `idx_batchno_status_productid` (`batch_no`, `status`, `product_id`);

DBA分析,該變更sql上線前,分頁查詢會走idx_batchno_userid索引,上線后則走idx_batchno_status_productid,而該索引存在大量重復記錄,導致每次分頁查詢的數據都可能和之前的重復。

為什么索引有重復記錄時,分頁查詢的數據就可能與之前的重復呢?在網上搜了下,這里貼一篇官方的文檔:https://dev.mysql.com/doc/refman/5.6/en/limit-optimization.html

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.

One factor that affects the execution plan is LIMIT, so an ORDER BY query with and without LIMIT may return rows in different orders.

If it is important to ensure the same row order with and without LIMIT, include additional columns in the ORDER BY clause to make the order deterministic.

在MySQL 5.6的版本上,優化器在遇到order by limit語句時,做了個查詢優化,即使用了priority queue(優先級隊列)。采用priority queue能夠根據limit N維護一個大小為N的堆,在排序的過程中,只用保留N條記錄即可。但堆排序是一個不穩定的排序算法,所以當排序字段值存在重復時,返回的數據順序可能會不一樣,其中一個影響因素就是limit。

解決方案:查詢語句加上order by id(保證排序字段的唯一性),上線后,問題得到解決。

但仍然存在兩個疑點:

  • 原來的索引idx_batchno_userid也會有重復記錄,為什么一直沒有爆出問題?

  • 線上的查詢語句只有limit,沒有order by,可以直接按照索引的有序性(索引聚簇表)進行讀取並分頁,不需要觸發priority queue優化。

3 問題定位

仔細觀察了兩次分頁查詢的結果集后發現,兩次結果集的數據順序分別對應兩個索引,即第一次分頁查詢走了索引idx_batchno_userid,第二次分頁查詢走了索引idx_batchno_status_productid,兩個索引的數據順序是不一樣的,從而導致兩次分頁查詢的數據存在重復。與DBA交流,MySQL的優化器會基於成本選擇最優的索引,而這兩個索引的成本相差不大。

這個結論在線下得到復現,但並不能穩定復現。在總結果集沒有變化的情況下,兩次分頁查詢分別走了不同索引的根因,還有待繼續深挖。

End

MySQL使用limit進行分頁查詢時,可能會出現重復數據,可以通過加上order by子句並保證排序字段的唯一性來解決。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM