https://www.cnblogs.com/aspirant/p/9214485.html 一步步分析為什麼B+樹適合作為索引的結構 以及索引原理 mysql的B+樹索引 查找使用了二分查找,redis 跳錶也使用了二分查找法,kafka查詢消息日誌也使用了二分查找法,二分查找法時間複雜度O(l ...
https://www.cnblogs.com/aspirant/p/9214485.html
一步步分析為什麼B+樹適合作為索引的結構 以及索引原理
mysql的B+樹索引 查找使用了二分查找,redis 跳錶也使用了二分查找法,kafka查詢消息日誌也使用了二分查找法,二分查找法時間複雜度O(logn);
參考:redis的索引底層的 跳錶原理 實現 聊聊Mysql索引和redis跳錶 ---redis的跳錶原理 時間複雜度O(logn)(阿裡)
參考:kafka如何實現高併發存儲-如何找到一條需要消費的數據(阿裡)
參考:二分查找法:各種排序演算法的時間複雜度和空間複雜度(阿裡)
在MySQL中,主要有四種類型的索引,分別為:B-Tree索引,Hash索引,Fulltext索引(MyISAM 表)和R-Tree索引,本文講的是B-Tree索引。
後面的索引原理一定要看,太重要了,阿裡兩個人都問這個mysql的索引原理
mysql使用了 B+索引:
B樹:有序數組+平衡多叉樹;
B+樹:有序數組鏈表+平衡多叉樹;
一、Mysql索引主要有兩種結構:B+Tree索引和Hash索引
(a) Inodb存儲引擎 預設是 B+Tree索引
(b) MyISAM 存儲引擎 預設是Fulltext索引;
(c)Memory 存儲引擎 預設 Hash索引;
Hash索引
mysql中,只有Memory(Memory表只存在記憶體中,斷電會消失,適用於臨時表)存儲引擎顯示支持Hash索引,是Memory表的預設索引類型,儘管Memory表也可以使用B+Tree索引。Hash索引把數據以hash形式組織起來,因此當查找某一條記錄的時候,速度非常快。但是因為hash結構,每個鍵只對應一個值,而且是散列的方式分佈。所以它並不支持範圍查找和排序等功能。
B+Tree索引
B+Tree是mysql使用最頻繁的一個索引數據結構,是Inodb和Myisam存儲引擎模式的索引類型。相對Hash索引,B+Tree在查找單條記錄的速度比不上Hash索引,但是因為更適合排序等操作,所以它更受歡迎。畢竟不可能只對資料庫進行單條記錄的操作。
帶順序訪問指針的B+Tree
B+Tree所有索引數據都在葉子節點上,並且增加了順序訪問指針,每個葉子節點都有指向相鄰葉子節點的指針。
這樣做是為了提高區間效率,例如查詢key為從18到49的所有數據記錄,當找到18後,只要順著節點和指針順序遍歷就可以以此向訪問到所有數據節點,極大提高了區間查詢效率。
大大減少磁碟I/O讀取
資料庫系統的設計者巧妙利用了磁碟預讀原理,將一個節點的大小設為等於一個頁,這樣每個節點需要一次I/O就可以完全載入。
什麼是索引
索引(Index)是幫助資料庫高效獲取數據的數據結構。索引是在基於資料庫表創建的,它包含一個表中某些列的值以及記錄對應的地址,並且把這些值存儲在一個數據結構中。最常見的就是使用哈希表、B+樹作為索引。
一般的應用系統,讀寫比例在10:1左右,而且插入操作和一般的更新操作很少出現性能問題,在生產環境中,我們遇到最多的,也是最容易出問題的,還是一些複雜的查詢操作,因此對查詢語句的優化顯然是重中之重。說起加速查詢,就不得不提到索引了。
為什麼要使用索引
我們知道,資料庫查詢是資料庫最主要的功能之一。而查詢速度當然是越快越好。而當數據量越來越大的時候,查詢花費的時間會隨之增長。而索引,可以加速數據的查詢。因為索引是有序排列的。
舉個例子來說,假設我們有一個資料庫表Employee,這個表分別有三個欄位:name,age,address。假設表中有1000條記錄。
假如沒有使用索引,當我們查詢名為“Jesus”的雇員的時候,即調用:
select name,age,address from Employee where name = 'Jesus';
此時資料庫不得不在Employee表中對這1000條記錄一條一條的進行判斷name欄位是否為“Jesus”。這也就是所謂的全表掃描。
而當我們在Employee表上的name欄位上創建索引時,當我們查詢名為“Jesus”的雇員時,會通過索引查找去查詢名為“Jesus”的雇員,因為該索引已經按照字母順序排列,因此要查找名為“Jesus”的記錄時會快很多,因為名字首字母為“J”的雇員都是排列在一起的。通過該索引,能獲取到表中對應的記錄。
舉例說明使用索引的好處
假設索引(索引是一種數據結構)是鏈表結構。每個節點存儲的是關鍵字欄位(這個例子中對應的是name屬性)以及該關鍵字欄位在資料庫表的對應的記錄的地址。而這些節點是根據name屬性排序的(即根據字母順序排序)。因此,當我們執行上面說的查找名為“Jesus”的sql語句時,資料庫會通過該索引來查詢,因為該鏈表是有序排列的,在我們找到第一個name屬性為“Jesus”的節點後,繼續往後找,當遇到name屬性不為“Jesus”的節點時,就無需再往後查找了,因為節點是根據name屬性有序排列的啊。假設第一個name=“Jesus”的節點是第499個節點,最後一個name=“Jesus”的節點是第500個節點,那麼只需要遍歷501個節點就可以了。當發現第501個節點的name欄位不為“Jesus”,後面的499個節點也就無需遍歷了。通過索引,我們就找到了name為“Jesus”的節點,而通過該節點的另一個屬性(關鍵字欄位在資料庫表的對應的記錄的地址),我們就能獲取到Employee表中滿足條件name=“Jesus”的記錄了。
通過使用索引,查詢判斷的次數就從1000次縮小到了501次了。起到了加速了查詢效率。但實際上資料庫中索引的結構,並不是鏈表結構。
資料庫中使用什麼數據結構作為索引
資料庫中實際使用的索引並不會是鏈表結構,因為效率太低了。
我們知道鏈表的查詢效率是O(n)。就像上面的例子,遍歷了501次才找到第一條符合條件的記錄,這是很低效的。而我們知道,數組+二分查找的效率是O(lgn),但是數組的插入元素以及刪除元素的效率很低,因此使用數組做為索引結構並不合適。
另外,在選擇資料庫索引的結構的時候,要考慮到另一個問題。索引是存在於磁碟中,當索引非常大的時候,達到幾個G的時候,無法一次載入到記憶體中。
考慮到上面兩個因素,資料庫中索引使用的是樹形結構。
各種樹的名字
有這麼幾種樹:
B-Tree B+-Tree B*-Tree
首先要明白三種樹名中的“-”起到的是分隔的作用,並不是“減”的意思。
因此正確的翻譯應該是B樹,B+樹,B*樹
。而不是B-樹,B+樹,B*樹
。因此,當你聽到別人說“B減樹”的時候,要明白它指的是B-Tree。即B樹和B-樹是同一種樹。
為什麼要強調上面這一點呢,因為有的博文中寫的是:B樹是二叉樹,B-樹是多路搜索樹。
然而B樹和B-樹都是指B-Tree。引用維基百科上的話:
B-tree Not to be confused with Binary tree.
也就是說,B-Tree並不是Binart tree。B-Tree的中文名是平衡多路搜索樹。
(B樹的相關介紹在下麵)
平衡二叉樹
樹形結構是電腦系統里最重要的數據結構。
我們知道,二叉樹的查找的時間複雜度是O(log2N),其查找效率與深度有關,而普通的二叉樹可能由於內部節點排列問題退化成鏈表,這樣查找效率就會很低。因此平衡二叉樹是更好的選擇,因為它保持平衡,即通過旋轉調整結構保持最小的深度。其查找的時間複雜度也是O(log2N)。
但實際上,資料庫中索引的結構也並非AVL樹或更優秀的紅黑樹,儘管它的查詢的時間複雜度很低。
為什麼平衡二叉樹也不適合作為索引
之前說了平衡樹的查找時間複雜度是O(log2N),已經很不錯了,但還是不適合作為索引結構。那麼肯定是有一種更適合作為索引的數據結構。那麼這個更適合作為索引的數據結構,難道是查找的時間複雜度更低嗎?並不是。這種作為索引的數據結構的查找的時間複雜度也近似O(log2N)。
那為什麼平衡二叉樹不適合作為索引呢?
索引是存在於索引文件中,是存在於磁碟中的。因為索引通常是很大的,因此無法一次將全部索引載入到記憶體當中,因此每次只能從磁碟中讀取一個磁碟頁的數據到記憶體中。而這個磁碟的讀取的速度較記憶體中的讀取速度而言是差了好幾個級別。
註意,我們說的平衡二叉樹結構,指的是邏輯結構上的平衡二叉樹,其物理實現是數組。然後由於在邏輯結構上相近的節點在物理結構上可能會差很遠。因此,每次讀取的磁碟頁的數據中有許多是用不上的。因此,查找過程中要進行許多次的磁碟讀取操作。
而適合作為索引的結構應該是儘可能少的執行磁碟IO操作,因為執行磁碟IO操作非常的耗時。因此,平衡二叉樹並不適合作為索引結構。
B-Tree適合作為索引
平衡二叉樹不適合作為索引。那麼什麼才適合作為索引——B樹。
平衡二叉樹沒能充分利用磁碟預讀功能,而B樹是為了充分利用磁碟預讀功能來而創建的一種數據結構,也就是說B樹就是為了作為索引才被髮明出來的的。
來看看關於“局部性原理與磁碟預讀”的知識:
局部性原理與磁碟預讀: 由於存儲介質的特性,磁碟本身存取就比主存慢很多,再加上機械運動耗費,磁碟的存取速度往往是主存的幾百分分之一,因此為了提高效率,要儘量減少磁碟I/O。為了達到這個目的,磁碟往往不是嚴格按需讀取,而是每次都會預讀,即使只需要一個位元組,磁碟也會從這個位置開始,順序向後讀取一定長度的數據放入記憶體。這樣做的理論依據是電腦科學中著名的局部性原理: 當一個數據被用到時,其附近的數據也通常會馬上被使用。 程式運行期間所需要的數據通常比較集中。 由於磁碟順序讀取的效率很高(不需要尋道時間,只需很少的旋轉時間),因此對於具有局部性的程式來說,預讀可以提高I/O效率。
搞清楚上面的意思。磁碟預讀是具體實現,其理論依據是局部性原理。
為什麼說紅黑樹沒能充分利用磁碟預讀功能,引用一篇博文的一段話:
紅黑樹這種結構,h明顯要深的多。由於邏輯上很近的節點(父子)物理上可能很遠,無法利用局部性,所以紅黑樹的I/O漸進複雜度也為O(h),效率明顯比B-Tree差很多。
也就是說,使用紅黑樹(平衡二叉樹)結構的話,每次磁碟預讀中的很多數據是用不上的數據。因此,它沒能利用好磁碟預讀的提供的數據。然後又由於深度大(較B樹而言),所以進行的磁碟IO操作更多。
B樹的每個節點可以存儲多個關鍵字,它將節點大小設置為磁碟頁的大小,充分利用了磁碟預讀的功能。每次讀取磁碟頁時就會讀取一整個節點。也正因每個節點存儲著非常多個關鍵字,樹的深度就會非常的小。進而要執行的磁碟讀取操作次數就會非常少,更多的是在記憶體中對讀取進來的數據進行查找。
B樹的查詢,主要發生在記憶體中,而平衡二叉樹的查詢,則是發生在磁碟讀取中。因此,雖然B樹查詢查詢的次數不比平衡二叉樹的次數少,但是相比起磁碟IO速度,記憶體中比較的耗時就可以忽略不計了。因此,B樹更適合作為索引。
比B樹更適合作為索引的結構——B+樹
比B樹更適合作為索引的結構是B+樹。MySQL中也是使用B+樹作為索引。它是B樹的變種,因此是基於B樹來改進的。為什麼B+樹會比B樹更加優秀呢?
B樹:有序數組+平衡多叉樹;
B+樹:有序數組鏈表+平衡多叉樹;
B+樹的關鍵字全部存放在葉子節點中,非葉子節點用來做索引,而葉子節點中有一個指針指向一下個葉子節點。做這個優化的目的是為了提高區間訪問的性能。而正是這個特性決定了B+樹更適合用來存儲外部數據。
引用一段話:
走進搜索引擎的作者梁斌老師針對B樹、B+樹給出了他的意見(為了真實性,特引用其原話,未作任何改動): “B+樹還有一個最大的好處,方便掃庫,B樹必須用中序遍歷的方法按序掃庫,而B+樹直接從葉子結點挨個掃一遍就完了,B+樹支持range-query非常方便,而B樹不支持。這是資料庫選用B+樹的最主要原因。 比如要查 5-10之間的,B+樹一把到5這個標記,再一把到10,然後串起來就行了,B樹就非常麻煩。B樹的好處,就是成功查詢特別有利,因為樹的高度總體要比B+樹矮。不成功的情況下,B樹也比B+樹稍稍占一點點便宜。 B樹比如你的例子中查,17的話,一把就得到結果了, 有很多基於頻率的搜索是選用B樹,越頻繁query的結點越往根上走,前提是需要對query做統計,而且要對key做一些變化。 另外B樹也好B+樹也好,根或者上面幾層因為被反覆query,所以這幾塊基本都在記憶體中,不會出現讀磁碟IO,一般已啟動的時候,就會主動換入記憶體。”
舉個例子來對比。
B樹:
比如說,我們要查找關鍵字範圍在3到7的關鍵字,在找到第一個符合條件的數字3後,訪問完第一個關鍵字所在的塊後,得遍歷這個B樹,獲取下一個塊,直到遇到一個不符合條件的關鍵字。遍歷的過程是比較複雜的。
B+樹(葉節點保存數據,其他的節點 全部存放索引):
相比之下,B+樹的基於範圍的查詢簡潔很多。由於葉子節點有指向下一個葉子節點的指針,因此從塊1到塊2的訪問,通過塊1指向塊2的指針即可。從塊2到塊3也是通過一個指針即可。
引用一篇博文中網友評論的一段話:
資料庫索引採用B+樹的主要原因是B樹在提高了磁碟IO性能的同時並沒有解決元素遍歷的效率低下的問題。正是為瞭解決這個問題,B+樹應運而生。
B+樹只要遍歷葉子節點就可以實現整棵樹的遍歷。而且在資料庫中基於範圍的查詢是非常頻繁的,而B樹不支持這樣的操作(或者說效率太低)。
正如上面所說,在資料庫中基於範圍的查詢是非常頻繁的,因此MySQL最終選擇的索引結構是B+樹而不是B樹。
二、索引的原理
一 索引原理
索引的目的在於提高查詢效率,與我們查閱圖書所用的目錄是一個道理:先定位到章,然後定位到該章下的一個小節,然後找到頁數。相似的例子還有:查字典,查火車車次,飛機航班等
本質都是:通過不斷地縮小想要獲取數據的範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是說,有了這種索引機制,我們可以總是用同一種查找方式來鎖定數據。
資料庫也是一樣,但顯然要複雜的多,因為不僅面臨著等值查詢,還有範圍查詢(>、<、between、in)、模糊查詢(like)、並集查詢(or)等等。資料庫應該選擇怎麼樣的方式來應對所有的問題呢?我們回想字典的例子,能不能把數據分成段,然後分段查詢呢?最簡單的如果1000條數據,1到100分成第一段,101到200分成第二段,201到300分成第三段......這樣查第250條數據,只要找第三段就可以了,一下子去除了90%的無效數據。但如果是1千萬的記錄呢,分成幾段比較好?稍有演算法基礎的同學會想到搜索樹,其平均複雜度是lgN,具有不錯的查詢性能。但這裡我們忽略了一個關鍵的問題,複雜度模型是基於每次相同的操作成本來考慮的。而資料庫實現比較複雜,一方面數據是保存在磁碟上的,另外一方面為了提高性能,每次又可以把部分數據讀入記憶體來計算,因為我們知道訪問磁碟的成本大概是訪問記憶體的十萬倍左右,所以簡單的搜索樹難以滿足複雜的應用場景。
二 磁碟IO與預讀
考慮到磁碟IO是非常高昂的操作,電腦操作系統做了一些優化,當一次IO時,不光把當前磁碟地址的數據,而是把相鄰的數據也都讀取到記憶體緩衝區內,因為局部預讀性原理告訴我們,當電腦訪問一個地址的數據的時候,與其相鄰的數據也會很快被訪問到。每一次IO讀取的數據我們稱之為一頁(page)。具體一頁有多大數據跟操作系統有關,一般為4k或8k,也就是我們讀取一頁內的數據時候,實際上才發生了一次IO,這個理論對於索引的數據結構設計非常有幫助。
三、索引的數據結構
任何一種數據結構都不是憑空產生的,一定會有它的背景和使用場景,我們現在總結一下,我們需要這種數據結構能夠做些什麼,其實很簡單,那就是:每次查找數據時把磁碟IO次數控制在一個很小的數量級,最好是常數數量級。那麼我們就想到如果一個高度可控的多路搜索樹是否能滿足需求呢?就這樣,b+樹應運而生。
如上圖,是一顆b+樹,關於b+樹的定義可以參見B+樹,這裡只說一些重點,淺藍色的塊我們稱之為一個磁碟塊,可以看到每個磁碟塊包含幾個數據項(深藍色所示)和指針(黃色所示),如磁碟塊1包含數據項17和35,包含指針P1、P2、P3,P1表示小於17的磁碟塊,P2表示在17和35之間的磁碟塊,P3表示大於35的磁碟塊。真實的數據存在於葉子節點即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非葉子節點只不存儲真實的數據,只存儲指引搜索方向的數據項,如17、35並不真實存在於數據表中。
###b+樹的查找過程
如圖所示,如果要查找數據項29,那麼首先會把磁碟塊1由磁碟載入到記憶體,此時發生一次IO,在記憶體中用二分查找確定29在17和35之間,鎖定磁碟塊1的P2指針,記憶體時間因為非常短(相比磁碟的IO)可以忽略不計,通過磁碟塊1的P2指針的磁碟地址把磁碟塊3由磁碟載入到記憶體,發生第二次IO,29在26和30之間,鎖定磁碟塊3的P2指針,通過指針載入磁碟塊8到記憶體,發生第三次IO,同時記憶體中做二分查找找到29,結束查詢,總計三次IO。真實的情況是,3層的b+樹可以表示上百萬的數據,如果上百萬的數據查找只需要三次IO,性能提高將是巨大的,如果沒有索引,每個數據項都要發生一次IO,那麼總共需要百萬次的IO,顯然成本非常非常高。
###b+樹性質
1.索引欄位要儘量的小:通過上面的分析,我們知道IO次數取決於b+數的高度h,假設當前數據表的數據為N,每個磁碟塊的數據項的數量是m,則有h=㏒(m+1)N,當數據量N一定的情況下,m越大,h越小;而m = 磁碟塊的大小 / 數據項的大小,磁碟塊的大小也就是一個數據頁的大小,是固定的,如果數據項占的空間越小,數據項的數量越多,樹的高度越低。這就是為什麼每個數據項,即索引欄位要儘量的小,比如int占4位元組,要比bigint8位元組少一半。這也是為什麼b+樹要求把真實的數據放到葉子節點而不是內層節點,一旦放到內層節點,磁碟塊的數據項會大幅度下降,導致樹增高。當數據項等於1時將會退化成線性表。
2.索引的最左匹配特性(即從左往右匹配):當b+樹的數據項是複合的數據結構,比如(name,age,sex)的時候,b+數是按照從左到右的順序來建立搜索樹的,比如當(張三,20,F)這樣的數據來檢索的時候,b+樹會優先比較name來確定下一步的所搜方向,如果name相同再依次比較age和sex,最後得到檢索的數據;但當(20,F)這樣的沒有name的數據來的時候,b+樹就不知道下一步該查哪個節點,因為建立搜索樹的時候name就是第一個比較因數,必須要先根據name來搜索才能知道下一步去哪裡查詢。比如當(張三,F)這樣的數據來檢索時,b+樹可以用name來指定搜索方向,但下一個欄位age的缺失,所以只能把名字等於張三的數據都找到,然後再匹配性別是F的數據了, 這個是非常重要的性質,即索引的最左匹配特性。
這也是經常考察的,比如 我定義了 A,B,C的聯合索引,如果 我只傳遞了 A,B 能走索引嗎?答案是能,因為最左側原理(百度問過)
補充一下. 全文索引(FULLTEXT)=mysql的 myISAM搜索引擎預設的索引類型
MySQL從3.23.23版開始支持全文索引和全文檢索,fulltext索引僅可用於 MyISAM 表;他們可以從CHAR、VARCHAR或TEXT列中作為CREATE TABLE語句的一部分被創建,或是隨後使用ALTER TABLE 或CREATE INDEX被添加。////對於較大的數據集,將你的資料輸入一個沒有FULLTEXT索引的表中,然後創建索引,其速度比把資料輸入現有FULLTEXT索引的速度更為快。不過切記對於大容量的數據表,生成全文索引是一個非常消耗時間非常消耗硬碟空間的做法。
文本欄位上的普通索引只能加快對出現在欄位內容最前面的字元串(也就是欄位內容開頭的字元)進行檢索操作。如果欄位里存放的是由幾個、甚至是多個單詞構成的較大段文字,普通索引就沒什麼作用了。這種檢索往往以LIKE %word%的形式出現,這對MySQL來說很複雜,如果需要處理的數據量很大,響應時間就會很長。
這類場合正是全文索引(full-text index)可以大顯身手的地方。在生成這種類型的索引時,MySQL將把在文本中出現的所有單詞創建為一份清單,查詢操作將根據這份清單去檢索有關的數據記錄。全文索引即可以隨數據表一同創建,也可以等日後有必要時再使用下麵這條命令添加:
ALTER TABLE table_name ADD FULLTEXT(column1, column2)
有了全文索引,就可以用SELECT查詢命令去檢索那些包含著一個或多個給定單詞的數據記錄了。下麵是這類查詢命令的基本語法:
SELECT * FROM table_name
WHERE MATCH(column1, column2) AGAINST('word1', 'word2', 'word3')
上面這條命令將把column1和column2欄位里有word1、word2和word3的數據記錄全部查詢出來。
四,索引使用註意事項
1,不要濫用索引
①,索引提高查詢速度,卻會降低更新表的速度,因為更新表時,mysql不僅要更新數據,保存數據,還要更新索引,保存索引
②,索引會占用磁碟空間
2,索引不會包含含有NULL值的列
複合索引只要有一列含有NULL值,那麼這一列對於此符合索引就是無效的,因此我們在設計資料庫設計時不要讓欄位的預設值為NULL。
3,MySQL查詢只是用一個索引
如果where字句中使用了索引的話,那麼order by中的列是不會使用索引的
4,like
like '%aaa%'不會使用索引而like "aaa%"可以使用索引
二、選擇索引的數據類型
Mysql支持很多數據類型,選擇合適的數據類型存儲數據對性能有很大的影響。
(1)越小的數據類型通常更好:越小的數據類型通常在磁碟、記憶體和cpu緩存中都需要更少的空間,處理起來更快。
(2)簡單的數據類型更好:整形數據比起字元,處理開銷更小,因為字元串的比較更複雜。在MySQL中,應用內置的日期和時間數據類型,而不是字元串來存儲時間;以及用整形數據存儲IP地址。
(3)儘量避免NULL:應該制定列為NOT NULL,除非你想存儲NULL。在MySQL中,含有空值的列很難進行查詢優化,因為他們使得索引、索引的統計信息以及比較運算更加複雜。
三、MySQL常見索引有:主鍵索引、唯一索引、普通索引、全文索引、組合索引
1,INDEX(普通索引):ALTER TABLE 'table_name' ADD INDEX index_name('col')
最基本的索引,沒有任何限制
2,UNIQUE(唯一索引):ALTER TABLE 'table_name' ADD UNIQUE('col')
與“普通索引”類似,不同的就是:索引列的值必須唯一,但允許有空值。
3,PRIMARY KEY(主鍵索引):ALTER TABLE 'table_name' ADD PRIMARY KEY('col')
是一種特殊的唯一索引,不允許有空值。
4,FULLTEXT(全文索引):ALTER TABLE 'table_name' ADD FULLTEXT('col')
僅可用於MyISAM和InoDB,針對較大的數據,生成全文索引很耗時耗空間
組合索引:ALTER TABLE 'table_name' ADD INDEX index_name('col1','col2','col3')
為了更多的提高mysql效率可建立組合索引,遵循“最左首碼”原則。創建複合索引應該將最常用(頻率)做限制條件的列放在最左邊,一次遞減。組合索引最左欄位用in是可以用到索引的。相當於建立了col1,col1col2,col1col2col3三個索引