自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

關(guān)于在 MySQL 排序中使用索引這件事!

數(shù)據(jù)庫(kù) MySQL
在 MySQL8 之前,默認(rèn)會(huì)按照 group by 的字段進(jìn)行排序,此時(shí)加上 order by null? 就是告訴 MySQL,不用幫我排序了,直接返回結(jié)果就行了,因?yàn)槿绻患?order by null,則可能會(huì)進(jìn)行 filesort 排序,降低查詢效率。

前面跟小伙伴們分享的索引相關(guān)的內(nèi)容,基本上都是在 where 子句中使用索引,實(shí)際上,索引也還有另外一個(gè)大的用處,那就是在排序中使用索引,今天我們就來(lái)聊聊這個(gè)話題。

1. 排序的兩種方式

MySQL 中想給查詢結(jié)果排序,我們只需要來(lái)一個(gè) order by 即可,SQL 很簡(jiǎn)單,底層實(shí)現(xiàn)起來(lái)整體上來(lái)說(shuō),有兩種不同的思路:

  1. filesort,有時(shí)候我們也將之稱為文件排序,這個(gè)名字有時(shí)候會(huì)給我們一些誤解,讓人以為是在磁盤上進(jìn)行排序的,然而實(shí)際上并不一定,數(shù)據(jù)量比較小的時(shí)候,直接在內(nèi)存中進(jìn)行排序就行了,只有當(dāng)在內(nèi)存中無(wú)法完成排序的時(shí)候,才會(huì)用到磁盤文件。
  2. 索引排序,由于 InnoDB 中的索引是按照 B+Tree 的形式將數(shù)據(jù)組織在一起的,B+Tree 中數(shù)據(jù)本身就是有序的,所以如果能夠利用好索引,排序的事情就會(huì)事半功倍。

一共就這兩種排序的方式,小伙伴們也發(fā)現(xiàn)了,如果我們的索引設(shè)計(jì)比較合理,最終能夠按照第 2 種方式進(jìn)行排序,那肯定是最好不過(guò)了。

不過(guò)這里需要注意一個(gè)細(xì)節(jié),第二種排序方式快有一個(gè)前提,那就是不需要回表,如果查詢的過(guò)程中需要回表,那么第二種方式就不一定快了。原因也簡(jiǎn)單:

  • 如果不需要回表,也就是我們想要查詢的數(shù)據(jù)都在索引樹上,索引樹上的數(shù)據(jù)本身又都是按照順序存儲(chǔ)的,那么查到數(shù)據(jù)直接返回即可,本身就是有序的。
  • 如果查詢的時(shí)候,索引樹上并沒有我們想要的字段,那么就需要回表,小伙伴們知道,回表基本上都是隨機(jī) IO 了,因?yàn)榛乇淼臅r(shí)候,主鍵值并不一定連續(xù),此時(shí)效率就會(huì)低一些。那么這個(gè)時(shí)候第二種排序方式的性能就不一定強(qiáng)于第一種了,當(dāng)然,這并無(wú)固定結(jié)論,還是要結(jié)合具體情況分析,這里我只是告訴小伙伴們有各種可能的情況。

2. 索引排序

如果我們想用上索引排序,那么需要滿足哪些條件呢?

還是以我們上篇文章的數(shù)據(jù)為例,假設(shè)我有如下表結(jié)構(gòu):

CREATE TABLE `user` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `age` int DEFAULT NULL,
  `address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `gender` varchar(2) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `user_prop_index` (`username`,`age`,`address`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

這個(gè)表中有一個(gè)聯(lián)合索引,聯(lián)合索引的字段包含 username、age 和 address 三個(gè)。

表中的數(shù)據(jù)如下:

id(主鍵)

username

age

address

gender

1

ab

99

深圳


2

bw

95

天津


3

cx

93

深圳


4

bc

80

上海


5

bg

85

重慶


6

ac

98

廣州


7

bw

99

???br>


8

ck

90

深圳


9

cc

92

武漢


10

af

88

北京


還是假設(shè) username、age、address 三個(gè)字段組成聯(lián)合索引,B+Tree 如下:

圖片

小伙伴們就想想,怎么樣查詢,查出來(lái)的結(jié)果是有序的?

給大家 1 分鐘總結(jié)一下。

我們來(lái)梳理下:只有當(dāng)索引的順序和 order by 子句的順序完全一致,并且所有列的排序方向也都一致的情況下,MySQL 才能通過(guò)索引來(lái)對(duì)結(jié)果進(jìn)行排序,同時(shí),如果是聯(lián)合索引,order by 子句也需要滿足最左匹配原則。

我舉幾個(gè)例子。

2.1 案例一

先來(lái)看如下 SQL:

select address from user order by username;

這個(gè)是查詢 address 字段,根據(jù) username 進(jìn)行排序。很明顯,我們想要的 address 字段就存在于這個(gè)聯(lián)合索引的 B+Tree 上,并且這個(gè)聯(lián)合索引的 B+Tree 就是按照 username 進(jìn)行升序排序的,所以這個(gè) SQL 就可以通過(guò)索引進(jìn)行排序,如下圖:

圖片

type:index 就說(shuō)明了 MySQL 使用了索引掃描來(lái)進(jìn)行排序的。

2.2 案例二

再來(lái)看下面這條 SQL:

select address from user order by username asc,age desc\G

這個(gè) SQL 還是查詢 address 字段,是根據(jù) username 和 age 進(jìn)行排序的,其中 username 是按照升序排序,age 則是按照倒序排序,小伙伴們想想,在前面這個(gè)聯(lián)合索引的 B+Tree 中,username 是升序的沒問(wèn)題,當(dāng) username 相同的時(shí)候,age 也是按照升序排序的,但是 SQL 中卻要一個(gè)升序一個(gè)倒序,顯然從索引樹中拿到的數(shù)據(jù)無(wú)法滿足這樣的條件,所以這個(gè)查詢并不會(huì)使用索引排序,如下圖:

圖片

Extra 中的 Using filesort 就說(shuō)明了這里需要文件排序,無(wú)法通過(guò)索引排序完成需求。

2.3 案例三

再來(lái)看如下 SQL:

select address from user order by username desc

這個(gè) SQL 和 2.1 小節(jié)的 SQL 相比就是排序的順序變了,第一個(gè) SQL 沒有寫順序,默認(rèn)就是升序,這個(gè)里邊寫了是按照倒序來(lái)排列。B+Tree 中的 username 是升序,那么這個(gè)能用到索引排序嗎?這個(gè)是可以使用到索引排序的,在 MySQL5.7 中,執(zhí)行計(jì)劃如下:

圖片

在 MySQL8.x 中,執(zhí)行計(jì)劃如下:

圖片

小伙伴們看到,區(qū)別在于 Extra 中多了一個(gè) Backward index scan。

這是啥意思呢?

在 MySQL8 之前,索引是可以被反向掃描的,但是反向掃描效率會(huì)低一些,所以小伙伴們看到,在 MySQL5.7 中用到了索引排序,而且也沒說(shuō)其他的,這其實(shí)就是索引反向掃描了。

從 MySQL8 開始,索引定義時(shí)候的降序關(guān)鍵字 DESC 將不再被忽略,索引樹在存儲(chǔ)數(shù)據(jù)的時(shí)候可以降序存儲(chǔ)了,這樣在將來(lái)查詢的時(shí)候掃描索引就可以按照正向掃描了,正向掃描效率相對(duì)于反向掃描效率會(huì)高一些。

這塊我來(lái)舉個(gè)例子說(shuō)明問(wèn)題。假設(shè)我有如下創(chuàng)建表的 SQL:

CREATE TABLE t (
  c1 INT, c2 INT,
  INDEX idx1 (c1 ASC, c2 ASC),
  INDEX idx2 (c1 ASC, c2 DESC),
  INDEX idx3 (c1 DESC, c2 ASC),
  INDEX idx4 (c1 DESC, c2 DESC)
);

當(dāng)我在 MySQL5.7 中執(zhí)行如上 SQL 之后,再來(lái)查看表的定義,結(jié)果如下:

圖片

可以看到,雖然我在執(zhí)行的時(shí)候定了索引字段的順序,但是這個(gè)順序?qū)嶋H上是被忽略了。

再來(lái)看看 MySQL8 中執(zhí)行之后的結(jié)果:

圖片

可以看到,在 MySQL8 中,索引定義時(shí)字段的順序被保留了。這印證了我們前面所說(shuō)的沒有問(wèn)題。

最后,回到我們的問(wèn)題,Backward index scan 表示優(yōu)化器在查詢的時(shí)候?qū)⒛軌蚴褂媒敌蛩饕?/p>

2.4 案例四

再來(lái)看如下 SQL:

select gender from user where username='ab' order by age

這個(gè) SQL 中已經(jīng)給 username 指定了具體的值了,在前面的 B+Tree 中,當(dāng) username 已經(jīng)確定的時(shí)候,那么接下來(lái)就是按照 age 排序的,如果 age 相同則是按照 address 排序,所以上面這個(gè) SQL 是可以通過(guò)索引排序的:

圖片

2.5 案例五

再來(lái)看如下 SQL:

select gender from user where username='ab' order by address

這個(gè) SQL 中 username 也是給指定了具體的值了,但是排序卻是按照 address 排序的,小伙伴們知道,當(dāng) username 確定后,首先是按照 age 排序,其次才是按照 address 排序,所以,對(duì)于上面這個(gè) SQL,從索引樹中讀取出來(lái)的數(shù)據(jù),順序并不一定是按照 address 排的,所以上面這個(gè) SQL 無(wú)法用到索引排序:

圖片

2.6 案例六

再來(lái)看下面這個(gè) SQL:

select gender from user where username like 'a%' order by age

這個(gè) SQL 中的查詢條件 username 是范圍搜索,當(dāng) username 是范圍搜索的時(shí)候,就無(wú)法保證相應(yīng)的 age 是有序的了,所以這個(gè) SQL 也無(wú)法使用索引排序:

圖片

另外需要注意的是,像查詢條件中的 IN 和 BETWEEN 這樣的關(guān)鍵字,也算是范圍搜索,如果 where 子句中出現(xiàn)這些關(guān)鍵字,也是有可能導(dǎo)致無(wú)法使用索引排序的。

2.7 案例七

再來(lái)看下面這個(gè) SQL:

select gender from user where username like 'a%' order by username,age

這個(gè)雖然 username 也是按照范圍搜索,但是最終排序的時(shí)候卻是按照 username 和 age 排序的,按照范圍搜索拿出來(lái)的 username 和 age 本身就是有序的,所以這里也可以使用索引排序:

圖片

2.8 案例八

再來(lái)看下面這個(gè) SQL:

select gender from user where username like 'a%' order by username,gender

這個(gè) SQL 就不用多說(shuō)了,排序字段中出現(xiàn)了索引之外的列,那肯定沒法使用索引排序了:

圖片

總之,就是當(dāng)我們根據(jù) where 子句中的條件從 B+Tree 中定位到數(shù)據(jù)之后,定位到的這個(gè)數(shù)據(jù)究竟是否有序?如果有序且是 SQL 中要求的順序,就能使用索引排序,否則就不可以。

現(xiàn)在我們?cè)賮?lái)回過(guò)頭看一下一開始的結(jié)論,大家這個(gè)時(shí)候應(yīng)該就好理解了:

只有當(dāng)索引的順序和 order by 子句的順序完全一致,并且所有列的排序方向也都一致的情況下,MySQL 才能通過(guò)索引來(lái)對(duì)結(jié)果進(jìn)行排序,同時(shí),如果是聯(lián)合索引,order by 子句也需要滿足最左匹配原則。

3. 其他情況

3.1 多表聯(lián)查

當(dāng)我們?cè)诓樵兊臅r(shí)候是多表連接查詢時(shí),如果用到了排序,那么 order by 子句中涉及到的字段,必須全部在第一個(gè)表中,此時(shí)才會(huì)用到索引排序。

松哥舉一個(gè) TienChin 項(xiàng)目中的例子,TienChin 中有一個(gè)活動(dòng)渠道表 tienchin_channel,還有一個(gè)活動(dòng)表 tienchin_activity,活動(dòng)表中引用到了渠道表的 id,我們來(lái)做如下一個(gè)多表聯(lián)合查詢:

select ta.name from tienchin_activity ta inner join tienchin_channel tc using(`channel_id`)

我們來(lái)看下這個(gè) SQL 的執(zhí)行計(jì)劃:

圖片

可以看到,在這個(gè)查詢中,優(yōu)化器將 ta 表作為了第一張表,tc 表作為了第二張表,那么根據(jù)前面的結(jié)論,如果使用第一個(gè)表中的索引排序,就會(huì)用到索引排序,第二張表的則用不了,我們來(lái)驗(yàn)證一下。

圖片

圖片

圖片

可以看到,如果是第一張表的索引,就用到了索引排序;如果是第二張表的索引,就沒有用到索引排序,如果兩張表的索引都用了,也不會(huì)使用索引排序。

3.2 order by null

還有一種特殊的情況就是 order by null,不知道有沒有小伙伴見到過(guò)有人這樣寫?

在 MySQL8 之前,默認(rèn)會(huì)按照 group by 的字段進(jìn)行排序,此時(shí)加上 order by null 就是告訴 MySQL,不用幫我排序了,直接返回結(jié)果就行了,因?yàn)槿绻患?nbsp;order by null,則可能會(huì)進(jìn)行 filesort 排序,降低查詢效率。

不過(guò)從 MySQL8 開始,默認(rèn)已經(jīng)不會(huì)按照 group by 字段排序了,所以這句現(xiàn)在其實(shí)可以不用寫了。

4. 小結(jié)

好啦,關(guān)于 MySQL 中的索引排序就和小伙伴們聊這么多,希望大家都有所收獲~

責(zé)任編輯:武曉燕 來(lái)源: 江南一點(diǎn)雨
相關(guān)推薦

2018-07-10 08:56:19

編程程序員開發(fā)

2021-12-14 15:45:27

KubernetesSecretLinux

2022-02-23 09:52:15

InnoDB數(shù)據(jù)索引

2023-07-03 22:28:19

6GHz6G技術(shù)

2017-11-06 10:50:49

服務(wù)器區(qū)域

2015-08-24 08:51:01

程序員讀書

2017-03-21 23:10:22

2023-09-01 09:00:00

2019-05-06 14:28:09

英特爾處理器架構(gòu)

2013-01-16 09:56:46

程序員跳槽面試

2022-10-19 09:05:45

編譯程序員后端

2015-09-16 14:20:25

2023-03-07 08:22:34

MySQL優(yōu)化器

2018-05-23 07:03:48

2015-02-28 10:45:24

App國(guó)內(nèi)國(guó)外

2023-05-15 09:06:39

2020-09-08 15:55:44

華為HMS生態(tài)

2017-09-04 16:03:46

MySQLMySQL索引索引

2016-04-11 11:24:55

Windows 10Linux注意事項(xiàng)

2017-03-06 10:31:30

醫(yī)療 IT
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)