一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

長這么大這個 MySQL bug 讓我大開眼界

 Fengsq501u81r4 2021-09-28

這周收到一個 sentry 報警,如下 SQL 查詢超時了。

select * from order_info where uid = 5837661 order by id asc limit 1

執(zhí)行show create table order_info 發(fā)現(xiàn)這個表其實是有加索引的

CREATE TABLE `order_info` (  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,  `uid` int(11) unsigned,  `order_status` tinyint(3) DEFAULT NULL,  ... 省略其它字段和索引  PRIMARY KEY (`id`),  KEY `idx_uid_stat` (`uid`,`order_status`),) ENGINE=InnoDB DEFAULT CHARSET=utf8

理論上執(zhí)行上述 SQL 會命中 idx_uid_stat 這個索引,但實際執(zhí)行 explain 查看

explain select * from order_info where uid = 5837661 order by id asc limit 1

可以看到它的 possible_keys(此 SQL 可能涉及到的索引) 是 idx_uid_stat,但實際上(key)用的卻是全表掃描

文章圖片1

我們知道 MySQL 是基于成本來選擇是基于全表掃描還是選擇某個索引來執(zhí)行最終的執(zhí)行計劃的,所以看起來是全表掃描的成本小于基于 idx_uid_stat 索引執(zhí)行的成本,不過我的第一感覺很奇怪,這條 SQL 雖然是回表,但它的 limit 是 1,也就是說只選擇了滿足 uid = 5837661 中的其中一條語句,就算回表也只回一條記錄,這種成本幾乎可以忽略不計,優(yōu)化器怎么會選擇全表掃描呢。

當然懷疑歸懷疑,為了查看 MySQL 優(yōu)化器為啥選擇了全表掃描,我打開了 optimizer_trace 來一探究竟

畫外音:在MySQL 5.6 及之后的版本中,我們可以使用 optimizer trace 功能查看優(yōu)化器生成執(zhí)行計劃的整個過程

使用 optimizer_trace 的具體過程如下

SET optimizer_trace='enabled=on';        // 打開 optimizer_traceSELECT * FROM order_info where uid = 5837661 order by id asc limit 1SELECT * FROM information_schema.OPTIMIZER_TRACE;    // 查看執(zhí)行計劃表SET optimizer_trace='enabled=off'; // 關(guān)閉 optimizer_trace

MySQL 優(yōu)化器首先會計算出全表掃描的成本,然后選出該 SQL 可能涉及到到的所有索引并且計算索引的成本,然后選出所有成本最小的那個來執(zhí)行,來看下 optimizer trace 給出的關(guān)鍵信息

{ 'rows_estimation': [ { 'table': '`rebate_order_info`', 'range_analysis': { 'table_scan': { 'rows': 21155996, 'cost': 4.45e6 // 全表掃描成本 } }, ... 'analyzing_range_alternatives': { 'range_scan_alternatives': [ { 'index': 'idx_uid_stat', 'ranges': [ '5837661 <= uid <= 5837661' ], 'index_dives_for_eq_ranges': true, 'rowid_ordered': false, 'using_mrr': false, 'index_only': false, 'rows': 255918, 'cost': 307103, // 使用idx_uid_stat索引的成本 'chosen': true } ], 'chosen_range_access_summary': { // 經(jīng)過上面的各個成本比較后選擇的最終結(jié)果 'range_access_plan': { 'type': 'range_scan', 'index': 'idx_uid_stat', // 可以看到最終選擇了idx_uid_stat這個索引來執(zhí)行 'rows': 255918, 'ranges': [ '58376617 <= uid <= 58376617' ] }, 'rows_for_plan': 255918, 'cost_for_plan': 307103, 'chosen': true } } ...

可以看到全表掃描的成本是 4.45e6,而選擇索引 idx_uid_stat 的成本是 307103,遠小于全表掃描的成本,而且從最終的選擇結(jié)果(
chosen_range_access_summary)來看,確實也是選擇了 idx_uid_stat 這個索引,但為啥從 explain 看到的選擇是執(zhí)行 PRIMARY 也就是全表掃描呢,難道這個執(zhí)行計劃有誤?

文章圖片2

仔細再看了一下這個執(zhí)行計劃,果然發(fā)現(xiàn)了貓膩,執(zhí)行計劃中有一個
reconsidering_access_paths_for_index_ordering 選擇引起了我的注意

{    'reconsidering_access_paths_for_index_ordering': {    'clause': 'ORDER BY',    'index_order_summary': {      'table': '`rebate_order_info`',      'index_provides_order': true,      'order_direction': 'asc',      'index': 'PRIMARY',    // 可以看到選擇了主鍵索引      'plan_changed': true,      'access_type': 'index_scan'        }    }}

這個選擇表示由于排序的原因再進行了一次索引選擇優(yōu)化,由于我們的 SQL 使用了 id 排序(order by id asc limit 1),優(yōu)化器最終選擇了 PRIMARY 也就是全表掃描來執(zhí)行,也就是說這個選擇會無視之前的基于索引成本的選擇,為什么會有這樣的一個選項呢,主要原因如下:

The short explanation is that the optimizer thinks — or should I say hopes — that scanning the whole table (which is already sorted by the id field) will find the limited rows quick enough, and that this will avoid a sort operation. So by trying to avoid a sort, the optimizer ends-up losing time scanning the table.

從這段解釋可以看出主要原因是由于我們使用了 order by id asc 這種基于 id 的排序?qū)懛?,?yōu)化器認為排序是個昂貴的操作,所以為了避免排序,并且它認為 limit n 的 n 如果很小的話即使使用全表掃描也能很快執(zhí)行完,這樣使用全表掃描也就避免了 id 的排序(全表掃描其實也就是基于 id 主鍵的聚簇索引的掃描,本身就是基于 id 排好序的)

如果這個選擇是對的那也罷了,然而實際上這個優(yōu)化卻是有 bug 的!實際選擇 idx_uid_stat 執(zhí)行會快得多(只要 28 ms)!網(wǎng)上有不少人反饋這個問題,而且出現(xiàn)這個問題基本只與 SQL 中出現(xiàn) order by id asc limit n這種寫法有關(guān),如果 n 比較小很大概率會走全表掃描,如果 n 比較大則會選擇正確的索引。

這個 bug 最早追溯到 2014 年,不少人都呼吁官方及時修正這個bug,可能是實現(xiàn)比較困難,直到 MySQL 5.7,8.0 都還沒解決,所以在官方修復前我們要盡量避免這種寫法,那么怎么避免呢,主要有兩種方案

  1. 使用 force index 來強制使用指定的索引,如下:
select * from order_info force index(idx_uid_stat) where uid = 5837661 order by id asc limit 1

這種寫法雖然可以,但不夠優(yōu)雅,如果這個索引被廢棄了咋辦?于是有了第二種比較優(yōu)雅的方案

  1. 使用 order by (id 0) 方案,如下
select * from order_info where uid = 5837661 order by (id 0) asc limit 1

這種方案也可以讓優(yōu)化器選擇正確的索引,更推薦!

原文鏈接:
https://www.cnblogs.com/xiekun/p/15333566.html

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    国语对白刺激高潮在线视频| 久久精品国产熟女精品| 久久91精品国产亚洲| 国产日韩欧美综合视频| 国产又大又硬又粗又黄| 国产99久久精品果冻传媒| 天堂网中文字幕在线视频| 欧美小黄片在线一级观看| 亚洲一区二区三区一区| 自拍偷女厕所拍偷区亚洲综合| 国产精品日本女优在线观看| 国产又猛又大又长又粗| 国产欧美一区二区久久| 久久一区内射污污内射亚洲| 久久一区内射污污内射亚洲| 国产色第一区不卡高清| 大胆裸体写真一区二区| 99久久精品午夜一区| 国产视频福利一区二区| 色婷婷丁香激情五月天| 欧美日韩免费黄片观看| 国内女人精品一区二区三区| 极品熟女一区二区三区| 黄色激情视频中文字幕| 亚洲一区二区三区免费的视频| 中文字幕亚洲精品乱码加勒比| 日韩欧美综合中文字幕 | 国产一区二区三区av在线| 精品al亚洲麻豆一区| 国产午夜精品亚洲精品国产| 日本高清视频在线播放| 男人和女人干逼的视频| 精品日韩国产高清毛片| 91午夜少妇极品福利| 国产永久免费高清在线精品| 日韩精品少妇人妻一区二区| 在线播放欧美精品一区| 东北女人的逼操的舒服吗| 樱井知香黑人一区二区| 国内精品一区二区欧美| 免费亚洲黄色在线观看|