MySQL實戰45講學習筆記:第十六講

1、今日內容概要

在你開發應用的時候,必定會常常碰到須要根據指定的字段排序來顯示結果的需求。仍是以咱們前面舉例用過的市民表爲例,假設你要查詢城市是「杭州」的全部人名字,而且按
照姓名排序返回前 1000 我的的姓名、年齡。前端

假設這個表的部分定義是這樣的:mysql

CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `city` varchar(16) NOT NULL,
  `name` varchar(16) NOT NULL,
  `age` int(11) NOT NULL,
  `addr` varchar(128) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `city` (`city`)
) ENGINE=InnoDB;

這時,你的 SQL 語句能夠這麼寫:算法

select city,name,age from t where city='杭州' order by name limit 1000  ;

這個語句看上去邏輯很清晰,可是你瞭解它的執行流程嗎?今天,我就和你聊聊這個語句是怎麼執行的,以及有什麼參數會影響執行的行爲sql

2、全字段排序

前面咱們介紹過索引,因此你如今就很清楚了,爲避免全表掃描,咱們須要在 city 字段加上索引。數據庫

一、全字段排序執行流程

在 city 字段上建立索引以後,咱們用 explain 命令來看看這個語句的執行狀況。bash

圖 1 使用 explain 命令查看語句的執行狀況session

一、Using filesort是什麼意思?性能

一、Extra 這個字段中的「Using filesort」表示的就是須要排序,優化

二、MySQL 會給每一個線程分配一塊內存用於排序,稱爲 sort_buffer。spa

爲了說明這個 SQL 查詢語句的執行過程,咱們先來看一下 city 這個索引的示意圖。

圖 2 city 字段的索引示意圖

從圖中能夠看到,知足 city='杭州’條件的行,是從 ID_X 到 ID_(X+N) 的這些記錄。

一般狀況下,這個語句執行流程以下所示 :

1. 初始化 sort_buffer,肯定放入 name、city、age 這三個字段;
2. 從索引 city 找到第一個知足 city='杭州’條件的主鍵 id,也就是圖中的 ID_X;
3. 到主鍵 id 索引取出整行,取 name、city、age 三個字段的值,存入 sort_buffer 中;
4. 從索引 city 取下一個記錄的主鍵 id;
5. 重複步驟 三、4 直到 city 的值不知足查詢條件爲止,對應的主鍵 id 也就是圖中的ID_Y;
6. 對 sort_buffer 中的數據按照字段 name 作快速排序;
7. 按照排序結果取前 1000 行返回給客戶端。

二、全字段排序圖解執行流程

咱們暫且把這個排序過程,稱爲全字段排序,執行流程的示意圖以下所示,下一篇文章中咱們還會用到這個排序。

圖 3 全字段排序

三、按 name 排序的動做都會在哪裏完成?

一、圖中「按 name 排序」這個動做,可能在內存中完成,

二、也可能須要使用外部排序,這取決於排序所需的內存和參數 sort_buffer_size。

三、sort_buffer_size,就是 MySQL 爲排序開闢的內存(sort_buffer)的大小。若是要排序的數據量小於 sort_buffer_size,排序就在內存中完成。

四、但若是排序數據量太大,內存放不下,則不得不利用磁盤臨時文件輔助排序。

一、如何肯定一個排序語句是否使用了臨時文件?

你能夠用下面介紹的方法,來肯定一個排序語句是否使用了臨時文件。

/* 打開 optimizer_trace,只對本線程有效 */
SET optimizer_trace='enabled=on'; 

/* @a 保存 Innodb_rows_read 的初始值 */
select VARIABLE_VALUE into @a from  performance_schema.session_status where variable_name = 'Innodb_rows_read';

/* 執行語句 */
select city, name,age from t where city='杭州' order by name limit 1000; 

/* 查看 OPTIMIZER_TRACE 輸出 */
SELECT * FROM `information_schema`.`OPTIMIZER_TRACE`\G

/* @b 保存 Innodb_rows_read 的當前值 */
select VARIABLE_VALUE into @b from performance_schema.session_status where variable_name = 'Innodb_rows_read';

/* 計算 Innodb_rows_read 差值 */
select @b-@a;

這個方法是經過查看 OPTIMIZER_TRACE 的結果來確認的,你能夠從number_of_tmp_files 中看到是否使用了臨時文件。

 圖 4 全排序的 OPTIMIZER_TRACE 部分結果

四、rowid 排序的 OPTIMIZER_TRACE 部分輸出字段說明

一、sort_mode 裏面的 packed_additional_fields 的意思是,排序過程對字符串作了「緊湊」處理。即便 name 字段的定義是 varchar(16),在排序過程當中仍是要按照實際長度來分配空間的。

二、同時,最後一個查詢語句 select @b-@a 的返回結果是 4000,表示整個執行過程只掃描了 4000 行。

這裏須要注意的是,爲了不對結論形成干擾,我把 internal_tmp_disk_storage_engine設置成 MyISAM。不然,select @b-@a 的結果會顯示爲 4001。

這是由於查詢 OPTIMIZER_TRACE 這個表時,須要用到臨時表,而internal_tmp_disk_storage_engine 的默認值是 InnoDB。若是使用的是 InnoDB 引擎的話,把數據從臨時表取出來的時候,會讓 Innodb_rows_read 的值加 1。

3、rowid 排序

在上面這個算法過程裏面,只對原表的數據讀了一遍,剩下的操做都是在 sort_buffer 和臨時文件中執行的。但這個算法有一個問題,就是若是查詢要返回的字段不少的話,那麼
sort_buffer 裏面要放的字段數太多,這樣內存裏可以同時放下的行數不多,要分紅不少個臨時文件,排序的性能會不好。

因此若是單行很大,這個方法效率不夠好。

那麼,若是 MySQL 認爲排序的單行長度太大會怎麼作呢?

接下來,我來修改一個參數,讓 MySQL 採用另一種算法。

SET max_length_for_sort_data = 16;

一、改變單行最大長度後有什麼改變?

  • 一、max_length_for_sort_data,是 MySQL 中專門控制用於排序的行數據的長度的一個參數。
  • 二、它的意思是,若是單行的長度超過這個值,MySQL 就認爲單行太大,要換一個算法。
  • 三、city、name、age 這三個字段的定義總長度是 36,我把 max_length_for_sort_data 設置爲 16,咱們再來看看計算過程有什麼改變。
  • 四、新的算法放入 sort_buffer 的字段,只有要排序的列(即 name 字段)和主鍵 id。

一、改變後的流程

但這時,排序的結果就由於少了 city 和 age 字段的值,不能直接返回了,整個執行流程就變成以下所示的樣子:

1. 初始化 sort_buffer,肯定放入兩個字段,即 name 和 id;
2. 從索引 city 找到第一個知足 city='杭州’條件的主鍵 id,也就是圖中的 ID_X;
3. 到主鍵 id 索引取出整行,取 name、id 這兩個字段,存入 sort_buffer 中;
4. 從索引 city 取下一個記錄的主鍵 id;
5. 重複步驟 三、4 直到不知足 city='杭州’條件爲止,也就是圖中的 ID_Y;
6. 對 sort_buffer 中的數據按照字段 name 進行排序;
7. 遍歷排序結果,取前 1000 行,並按照 id 的值回到原表中取出 city、name 和 age 三個字段返回給客戶端。

二、圖解改變後的流程

這個執行流程的示意圖以下,我把它稱爲 rowid 排序。

圖 5 rowid 排序


對比圖 3 的全字段排序流程圖你會發現,rowid 排序多訪問了一次表 t 的主鍵索引,就是步驟 7。

須要說明的是,最後的「結果集」是一個邏輯概念,實際上 MySQL 服務端從排序後的sort_buffer 中依次取出 id,而後到原表查到 city、name 和 age 這三個字段的結果,不

須要在服務端再耗費內存存儲結果,是直接返回給客戶端的。

根據這個說明過程和圖示,你能夠想一下,這個時候執行 select @b-@a,結果會是多少呢?

如今,咱們就來看看結果有什麼不一樣。

首先,圖中的 examined_rows 的值仍是 4000,表示用於排序的數據是 4000 行。可是select @b-@a 這個語句的值變成 5000 了。

由於這時候除了排序過程外,在排序完成後,還要根據 id 去原表取值。因爲語句是 limit1000,所以會多讀 1000 行。

圖 6 rowid 排序的 OPTIMIZER_TRACE 部分輸出

三、rowid 排序的 OPTIMIZER_TRACE 部分輸出字段說明

一、從 OPTIMIZER_TRACE 的結果中,你還能看到另外兩個信息也變了。

二、sort_mode 變成了 <sort_key, rowid>,表示參與排序的只有 name 和 id 這兩個字段。

三、number_of_tmp_files 變成 10 了,是由於這時候參與排序的行數雖然仍然是 4000行,可是每一行都變小了,所以須要排序的總數據量就變小了,須要的臨時文件也相應地變少了。

4、全字段排序 VS rowid 排序

咱們來分析一下,從這兩個執行流程裏,還能得出什麼結論。

一、內存足夠大會如何處理

若是 MySQL 實在是擔憂排序內存過小,會影響排序效率,纔會採用 rowid 排序算法,

這樣排序過程當中一次能夠排序更多行,可是須要再回到原表去取數據。

二、內存足夠小會如何處理

若是 MySQL 認爲內存足夠大,會優先選擇全字段排序,把須要的字段都放到 sort_buffer中,

這樣排序後就會直接從內存裏面返回查詢結果了,不用再回到原表去取數據。這也就體現了 MySQL 的一個設計思想:若是內存夠,就要多利用內存,儘可能減小磁盤訪問。

對於 InnoDB 表來講,rowid 排序會要求回表多形成磁盤讀,所以不會被優先選擇。這個結論看上去有點廢話的感受,可是你要記住它,下一篇文章咱們就會用到。

一、是否是全部的 order by 都須要排序操做呢?

看到這裏,你就瞭解了,MySQL 作排序是一個成本比較高的操做。那麼你會問,是否是全部的 order by 都須要排序操做呢?若是不排序就能獲得正確的結果,那對系統的消耗
會小不少,語句的執行時間也會變得更短。

其實,並非全部的 order by 語句,都須要排序操做的。從上面分析的執行過程,咱們能夠看到,MySQL 之因此須要生成臨時表,而且在臨時表上作排序操做,其緣由是原來
的數據都是無序的。

你能夠設想下,若是可以保證從 city 這個索引上取出來的行,自然就是按照 name 遞增排序的話,是否是就能夠不用再排序了呢?

確實是這樣的。

因此,咱們能夠在這個市民表上建立一個 city 和 name 的聯合索引,對應的 SQL 語句是:

alter table t add index city_user(city, name);

做爲與 city 索引的對比,咱們來看看這個索引的示意圖。

圖 7 city 和 name 聯合索引示意圖

在這個索引裏面,咱們依然能夠用樹搜索的方式定位到第一個知足 city='杭州’的記錄,而且額外確保了,接下來按順序取「下一條記錄」的遍歷過程當中,只要 city 的值是杭州,
name 的值就必定是有序的。

二、不須要排序的查詢流程

這樣整個查詢過程的流程就變成了

1. 從索引 (city,name) 找到第一個知足 city='杭州’條件的主鍵 id;
2. 到主鍵 id 索引取出整行,取 name、city、age 三個字段的值,做爲結果集的一部分直接返回;
3. 從索引 (city,name) 取下一個記錄主鍵 id;
4. 重複步驟 二、3,直到查到第 1000 條記錄,或者是不知足 city='杭州’條件時循環結束。

圖 8 引入 (city,name) 聯合索引後,查詢語句的執行計劃

三、不須要排序查詢執行計劃

能夠看到,這個查詢過程不須要臨時表,也不須要排序。接下來,咱們用 explain 的結果來印證一下

圖 9 引入 (city,name) 聯合索引後,查詢語句的執行計劃

從圖中能夠看到,Extra 字段中沒有 Using filesort 了,也就是不須要排序了。並且因爲(city,name) 這個聯合索引自己有序,因此這個查詢也不用把 4000 行全都讀一遍,只要找
到知足條件的前 1000 條記錄就能夠退出了。也就是說,在咱們這個例子裏,只須要掃描1000 次。

四、這個語句的執行流程有沒有可能進一步簡化呢?

既然說到這裏了,咱們再往前討論,這個語句的執行流程有沒有可能進一步簡化呢?不知道你還記不記得,我在第 5 篇文章《 深刻淺出索引(下)》中,和你介紹的覆蓋索引。

這裏咱們能夠再稍微複習一下。覆蓋索引是指,索引上的信息足夠知足查詢請求,不須要再回到主鍵索引上去取數據。

按照覆蓋索引的概念,咱們能夠再優化一下這個查詢語句的執行流程。

針對這個查詢,咱們能夠建立一個 city、name 和 age 的聯合索引,對應的 SQL 語句就是:

alter table t add index city_user_age(city, name, age);

一、查詢語句的執行流程?

這時,對於 city 字段的值相同的行來講,仍是按照 name 字段的值遞增排序的,此時的查詢語句也就再也不須要排序了。這樣整個查詢語句的執行流程就變成了:

1. 從索引 (city,name,age) 找到第一個知足 city='杭州’條件的記錄,取出其中的 city、name 和 age 這三個字段的值,做爲結果集的一部分直接返回;

2. 從索引 (city,name,age) 取下一個記錄,一樣取出這三個字段的值,做爲結果集的一部分直接返回;

3. 重複執行步驟 2,直到查到第 1000 條記錄,或者是不知足 city='杭州’條件時循環結束。

圖 10 引入 (city,name,age) 聯合索引後,查詢語句的執行流程

二、查詢語句的執行計劃

而後,咱們再來看看 explain 的結果。

圖 11 引入 (city,name,age) 聯合索引後,查詢語句的執行計劃


能夠看到,Extra 字段裏面多了「Using index」,表示的就是使用了覆蓋索引,性能上會快不少。

固然,這裏並非說要爲了每一個查詢能用上覆蓋索引,就要把語句中涉及的字段都建上聯合索引,畢竟索引仍是有維護代價的。這是一個須要權衡的決定。

5、小結

今天這篇文章,我和你介紹了 MySQL 裏面 order by 語句的幾種算法流程。

在開發系統的時候,你老是不可避免地會使用到 order by 語句。你內心要清楚每一個語句的排序邏輯是怎麼實現的,還要可以分析出在最壞狀況下,每一個語句的執行對系統資源的
消耗,這樣才能作到下筆若有神,不犯低級錯誤。

最後,我給你留下一個思考題吧。

假設你的表裏面已經有了 city_name(city, name) 這個聯合索引,而後你要查杭州和蘇州兩個城市中全部的市民的姓名,而且按名字排序,顯示前 100 條記錄。若是 SQL 查詢語
句是這麼寫的 :

mysql> select * from t where city in ('杭州'," 蘇州 ") order by name limit 100;

那麼,這個語句執行的時候會有排序過程嗎,爲何?

若是業務端代碼由你來開發,須要實現一個在數據庫端不須要排序的方案,你會怎麼實現呢?

進一步地,若是有分頁需求,要顯示第 101 頁,也就是說語句最後要改爲 「limit10000,100」, 你的實現方法又會是什麼呢?

你能夠把你的思考和觀點寫在留言區裏,我會在下一篇文章的末尾和你討論這個問題。感謝你的收聽,也歡迎你把這篇文章分享給更多的朋友一塊兒閱讀。

6、上期問題時間

上期的問題是,當 MySQL 去更新一行,可是要修改的值跟原來的值是相同的,這時候MySQL 會真的去執行一次修改嗎?仍是看到值相同就直接返回呢?

這是第一次咱們課後問題的三個選項都有同窗選的,因此我要和你須要詳細說明一下。

第一個選項是,MySQL 讀出數據,發現值與原來相同,不更新,直接返回,執行結束。這裏咱們能夠用一個鎖實驗來確認

假設,當前表 t 裏的值是 (1,2)。

圖 12 鎖驗證方式

session B 的 update 語句被 blocked 了,加鎖這個動做是 InnoDB 才能作的,因此排除選項 1。

第二個選項是,MySQL 調用了 InnoDB 引擎提供的接口,可是引擎發現值與原來相同,不更新,直接返回。有沒有這種可能呢?這裏我用一個可見性實驗來確認。

假設當前表裏的值是 (1,2)。

圖 13 可見性驗證方式

session A 的第二個 select 語句是一致性讀(快照讀),它是不能看見 session B 的更新的。

如今它返回的是 (1,3),表示它看見了某個新的版本,這個版本只能是 session A 本身的update 語句作更新的時候生成。(若是你對這個邏輯有疑惑的話,能夠回顧下第 8 篇文
章《事務究竟是隔離的仍是不隔離的?》中的相關內容)

因此,咱們上期思考題的答案應該是選項 3,即:InnoDB 認真執行了「把這個值修改爲(1,2)"這個操做,該加鎖的加鎖,該更新的更新。

而後你會說,MySQL 怎麼這麼笨,就不會更新前判斷一下值是否是相同嗎?若是判斷一下,不就不用浪費 InnoDB 操做,多去更新一次了?

其實 MySQL 是確認了的。只是在這個語句裏面,MySQL 認爲讀出來的值,只有一個肯定的 (id=1), 而要寫的是 (a=3),只從這兩個信息是看不出來「不須要修改」的。

做爲驗證,你能夠看一下下面這個例子。

圖 14 可見性驗證方式 -- 對照

7、補充說明:

上面咱們的驗證結果都是在 binlog_format=statement 格式下進行的。

@didiren 補充了一個 case, 若是是 binlog_format=row 而且binlog_row_image=FULL 的時候,因爲 MySQL 須要在 binlog 裏面記錄全部的字段,
因此在讀數據的時候就會把全部數據都讀出來了。

根據上面說的規則,「既然讀了數據,就會判斷」, 所以在這時候,select * from twhere id=1,結果就是「返回 (1,2)」。

同理,若是是 binlog_row_image=NOBLOB, 會讀出除 blob 外的全部字段,在咱們這個例子裏,結果仍是「返回 (1,2)」。

對應的代碼如圖 15 所示。這是 MySQL 5.6 版本引入的,在此以前我沒有看過。因此,特此說明。

圖 15 binlog_row_image=FULL 讀字段邏輯

相似的,@mahonebags 同窗提到了 timestamp 字段的問題。結論是:若是表中有timestamp 字段並且設置了自動更新的話,那麼更新「別的字段」的時候,MySQL 會讀
入全部涉及的字段,這樣經過判斷,就會發現不須要修改。

這兩個點我會在後面講更新性能的文章中再展開。

8、經典留言


一、某、人

一、回答下@發條橙子同窗的問題:

問題一:

1)無條件查詢若是隻有order by create_time,即使create_time上有索引,也不會使用到。
由於優化器認爲走二級索引再去回表成本比全表掃描排序更高。
因此選擇走全表掃描,而後根據老師講的兩種方式選擇一種來排序
2)無條件查詢可是是order by create_time limit m.若是m值較小,是能夠走索引的.
由於優化器認爲根據索引有序性去回表查數據,而後獲得m條數據,就能夠終止循環,那麼成本比全表掃描小,則選擇走二級索引。
即使沒有二級索引,mysql針對order by limit也作了優化,採用堆排序。這部分老師明天會講

問題二:
若是是group by a,a上不能使用索引的狀況,是走rowid排序。
若是是group by limit,不能使用索引的狀況,是走堆排序
若是是隻有group by a,a上有索引的狀況,又根據選取值不一樣,索引的掃描方式又有不一樣
select * from t group by a --走的是索引全掃描,至於這裏爲何選擇走索引全掃描,還須要老師解惑下
select a from t group by a --走的是索引鬆散掃描,也就說只須要掃描每組的第一行數據便可,不用掃描每一行的值

問題三:
bigint和int加數字都不影響能存儲的值。
bigint(1)和bigint(19)都能存儲2^64-1範圍內的值,int是2^32-1。只是有些前端會根據括號裏來截取顯示而已。建議不加varchar()就必須帶,由於varchar()括號裏的數字表明能存多少字符。假設varchar(2),就只能存兩個字符,無論是中文仍是英文。目前來看varchar()這個值能夠設得稍稍大點,由於內存是按照實際的大小來分配內存空間的,不是按照值來預分配的。

二、老師我有幾個問題:

1.我仍是想在確認以前問的問題。一個長鏈接,一條sql申請了sort_buffer_size等一系列的會話級別的內存,sql成功執行完,該鏈接變爲sleep狀態。這些內存只是內容會被狀況,可是佔用的內存空間不會釋放?
2.假設要給a值加1,執行器先找引擎取a=1的行,而後執行器給a+1,在調用接口寫入a+1了數據。那麼加鎖不該該是在執行器第一次去取數據時,引擎層就加該加的鎖?爲何要等到第二次調用寫入數據時,才加鎖。第一次和第二次之間,難道不會被其餘事務修改嗎?若是沒有鎖保證
3.始終沒太明白堆排序是採用的什麼算法使得只須要對limit的數據進行排序就能夠,而不是排序全部的數據在取前m條。--不過這裏期待明天的文章

三、做者回復: 發條橙子同窗的問題:

問題1:你回答得比我回復的答案還好!👍🏿
問題2:這個後面咱們展開哈,要配圖才能說得清😄
問題3:回答得也很好,須要注意的是255這個邊界。小於255都須要一個字節記錄長度,超過255就須要兩個字節

四、你的問題:#好問題_#

1. 排序相關的內存在排序後就free掉還給系統了
2. 讀的時候加了寫鎖的
3. 堆排序要讀全部行的,只讀一次,我估計你已經理解對了😄

二、XD

老師,基於早上知道的sort_buffer是在server層,我從新理解了下rowid排序的過程,

1,執行器查看錶定義,發現name、city、age字段的長度之和超過max_length_for_sort_data,因此初始化sort_buffer的時候只放入id和name字段。
2,執行器調用存儲引擎的讀數據接口,依次獲取知足條件的數據的id和name,存入sort_buffer。
3,排序。
4,執行器根據limit條件篩選出id,再次調用引擎讀數據的接口獲取相應的數據,返回客戶端。
整個過程其實是被執行器拆成了兩次查詢,共調用兩次存儲層的讀數據接口,因此總的掃描行數須要相加。(@b-@a=5000)

可是對於using index condition的場景,執行器只調用了一次查詢接口,回表是由存儲層來完成的,因此掃描行數只算一次,即只算走索引搜索的過程當中掃描的行數。(@b-@a只會是4000)

不知道這麼理解對不對?

做者回復:

不只對,並且很是好!👍👍

把兩個知識點連起來了。是的:
1. rows_examined就是「server層調用引擎取一行的時候」加1;
2. 引擎內部本身調用,讀取行,不加1;

再補充一個例子:
加索引的時候,也要掃描全表,但若是是inplace DDL(@第13篇),你會看到掃描行數是0,也是由於這些掃描動做都是引擎內部本身調用的。

三、波波

1.MySQL會爲每一個線程分配一個內存(sort_buffer)用於排序該內存大小爲sort_buffer_size

1>若是排序的數據量小於sort_buffer_size,排序將會在內存中完成
2>若是排序數據量很大,內存中沒法存下這麼多數據,則會使用磁盤臨時文件來輔助排序,也稱外部排序
3>在使用外部排序時,MySQL會分紅好幾份單獨的臨時文件用來存放排序後的數據,而後在將這些文件合併成一個大文件

2.mysql會經過遍歷索引將知足條件的數據讀取到sort_buffer,而且按照排序字段進行快速排序

1>若是查詢的字段不包含在輔助索引中,須要按照輔助索引記錄的主鍵返回彙集索引取出所需字段
2>該方式會形成隨機IO,在MySQL5.6提供了MRR的機制,會將輔助索引匹配記錄的主鍵取出來在內存中進行排序,而後在回表
3>按照狀況創建聯合索引來避免排序所帶來的性能損耗,容許的狀況下也能夠創建覆蓋索引來避免回表

三、全字段排序

1.經過索引將所需的字段所有讀取到sort_buffer中
2.按照排序字段進行排序
3.將結果集返回給客戶端

缺點:

1.形成sort_buffer中存放不下不少數據,由於除了排序字段還存放其餘字段,對sort_buffer的利用效率不高
2.當所需排序數據量很大時,會有不少的臨時文件,排序性能也會不好

優勢:MySQL認爲內存足夠大時會優先選擇全字段排序,由於這種方式比rowid 排序避免了一次回表操做

四、rowid排序

1.經過控制排序的行數據的長度來讓sort_buffer中儘量多的存放數據,max_length_for_sort_data
2.只將須要排序的字段和主鍵讀取到sort_buffer中,並按照排序字段進行排序
3.按照排序後的順序,取id進行回表取出想要獲取的數據
4.將結果集返回給客戶端

優勢:更好的利用內存的sort_buffer進行排序操做,儘可能減小對磁盤的訪問

缺點:回表的操做是隨機IO,會形成大量的隨機讀,不必定就比全字段排序減小對磁盤的訪問

相關文章
相關標籤/搜索