轉自:http://database.ctocio.com.cn/353/11664853.shtmlhtml
另外很不錯的對於索引及索引優化的文章:mysql
http://www.cnblogs.com/magialmoon/archive/2013/11/23/3439042.htmlsql
http://www.cnblogs.com/baochuan/archive/2012/05/23/2513224.html 數據庫
索引的使用函數
示例數據庫性能
爲了討論索引策略,須要一個數據量不算小的數據庫做爲示例。本文選用MySQL官方文檔中提供的示例數據庫之一:employees。這個數據庫關係複雜度適中,且數據量較大。下圖是這個數據庫的E-R關係圖(引用自MySQL官方手冊):學習
MySQL官方文檔中關於此數據庫的頁面爲http://dev.mysql.com/doc/employee/en/employee.html。裏面詳細介紹了此數據庫,並提供了下載地址和導入方法,若是有興趣導入此數據庫到本身的MySQL能夠參考文中內容。測試
最左前綴原理與相關優化優化
高效使用索引的首要條件是知道什麼樣的查詢會使用到索引,這個問題和B+Tree中的「最左前綴原理」有關,下面經過例子說明最左前綴原理。網站
這裏先說一下聯合索引的概念。在上文中,咱們都是假設索引只引用了單個的列,實際上,MySQL中的索引能夠以必定順序引用多個列,這種索引叫作聯合索引,通常的,一個聯合索引是一個有序元組,其中各個元素均爲數據表的一列,實際上要嚴格定義索引須要用到關係代數,可是這裏我不想討論太多關係代數的話題,由於那樣會顯得很枯燥,因此這裏就再也不作嚴格定義。另外,單列索引能夠當作聯合索引元素數爲1的特例。
以employees.titles表爲例,下面先查看其上都有哪些索引:
如下是代碼片斷: SHOW INDEX FROM employees.titles; +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Null | Index_type | +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+ | titles | 0 | PRIMARY | 1 | emp_no | A | NULL | | BTREE | | titles | 0 | PRIMARY | 2 | title | A | NULL | | BTREE | | titles | 0 | PRIMARY | 3 | from_date | A | 443308 | | BTREE | | titles | 1 | emp_no | 1 | emp_no | A | 443308 | | BTREE | +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+
從結果中能夠到titles表的主索引爲<emp_no, title,="" from_date="">,還有一個輔助索引<emp_no>。爲了不多個索引使事情變複雜(MySQL的SQL優化器在多索引時行爲比較複雜),這裏咱們將輔助索引drop掉:
如下是代碼片斷: ALTER TABLE employees.titles DROP INDEX emp_no;
這樣就能夠專心分析索引PRIMARY的行爲了。
狀況一:全列匹配。
如下是代碼片斷: EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND title='Senior Engineer' AND from_date='1986-06-26'; +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+ | 1 | SIMPLE | titles | const | PRIMARY | PRIMARY | 59 | const,const,const | 1 | | +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+
很明顯,當按照索引中全部列進行精確匹配(這裏精確匹配指「=」或「IN」匹配)時,索引能夠被用到。這裏有一點須要注意,理論上索引對順序是敏感的,但 是因爲MySQL的查詢優化器會自動調整where子句的條件順序以使用適合的索引,例如咱們將where中的條件順序顛倒:
如下是代碼片斷: EXPLAIN SELECT * FROM employees.titles WHERE from_date='1986-06-26' AND emp_no='10001' AND title='Senior Engineer'; +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+ | 1 | SIMPLE | titles | const | PRIMARY | PRIMARY | 59 | const,const,const | 1 | | +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+
效果是同樣的。
狀況二:最左前綴匹配。
如下是代碼片斷: EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001'; +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+ | 1 | SIMPLE | titles | ref | PRIMARY | PRIMARY | 4 | const | 1 | | +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+
當查詢條件精確匹配索引的左邊連續一個或幾個列時,如<emp_no>或<emp_no, title="">,因此能夠被用到,可是隻能用到一部分,即條件所組成的最左前綴。上面的查詢從分析結果看用到了PRIMARY索引,可是 key_len爲4,說明只用到了索引的第一列前綴。
狀況三:查詢條件用到了索引中列的精確匹配,可是中間某個條件未提供。
如下是代碼片斷: EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND from_date='1986-06-26'; +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+ | 1 | SIMPLE | titles | ref | PRIMARY | PRIMARY | 4 | const | 1 | Using where | +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+
此時索引使用狀況和狀況二相同,由於title未提供,因此查詢只用到了索引的第一列,然後面的from_date雖然也在索引中,可是因爲 title不存在而沒法和左前綴鏈接,所以須要對結果進行掃描過濾from_date(這裏因爲emp_no惟一,因此不存在掃描)。若是想讓 from_date也使用索引而不是where過濾,能夠增長一個輔助索引<emp_no, from_date="">,此時上面的查詢會使用這個索引。除此以外,還可使用一種稱之爲「隔離列」的優化方法,將emp_no與from_date 之間的「坑」填上。
首先咱們看下title一共有幾種不一樣的值:
如下是代碼片斷: SELECT DISTINCT(title) FROM employees.titles; +--------------------+ | title | +--------------------+ | Senior Engineer | | Staff | | Engineer | | Senior Staff | | Assistant Engineer | | Technique Leader | | Manager | +--------------------+
只有7種。在這種成爲「坑」的列值比較少的狀況下,能夠考慮用「IN」來填補這個「坑」從而造成最左前綴:
如下是代碼片斷: EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND title IN ('Senior Engineer', 'Staff', 'Engineer', 'Senior Staff', 'Assistant Engineer', 'Technique Leader', 'Manager') AND from_date='1986-06-26'; +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 59 | NULL | 7 | Using where | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
此次key_len爲59,說明索引被用全了,可是從type和rows看出IN實際上執行了一個range查詢,這裏檢查了7個key。看下兩種查詢的性能比較:
如下是代碼片斷: SHOW PROFILES; +----------+------------+-------------------------------------------------------------------------------+ | Query_ID | Duration | Query | +----------+------------+-------------------------------------------------------------------------------+ | 10 | 0.00058000 | SELECT * FROM employees.titles WHERE emp_no='10001' AND from_date='1986-06-26'| | 11 | 0.00052500 | SELECT * FROM employees.titles WHERE emp_no='10001' AND title IN ... | +----------+------------+-------------------------------------------------------------------------------+
「填坑」後性能提高了一點。若是通過emp_no篩選後餘下不少數據,則後者性能優點會更加明顯。固然,若是title的值不少,用填坑就不合適了,必須創建輔助索引。
狀況四:查詢條件沒有指定索引第一列。
如下是代碼片斷: EXPLAIN SELECT * FROM employees.titles WHERE from_date='1986-06-26'; +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+ | 1 | SIMPLE | titles | ALL | NULL | NULL | NULL | NULL | 443308 | Using where | +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
因爲不是最左前綴,索引這樣的查詢顯然用不到索引。
狀況五:匹配某列的前綴字符串。
1 如下是代碼片斷: 2 EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND title LIKE 'Senior%'; 3 view sourceprint? 4 +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ 5 | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 6 +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ 7 | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 56 | NULL | 1 | Using where | 8 +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
此時能夠用到索引,可是若是通配符不是隻出如今末尾,則沒法使用索引,例如like '%x%'或‘%x’都沒法使用到索引。
狀況六:範圍查詢。
1 如下是代碼片斷: 2 EXPLAIN SELECT * FROM employees.titles WHERE emp_no<'10010' and title='Senior Engineer'; 3 +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ 4 | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 5 +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ 6 | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 4 | NULL | 16 | Using where | 7 +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
範圍列能夠用到索引(必須是最左前綴),可是範圍列後面的列沒法用到索引。同時,索引最多用於一個範圍列,所以若是查詢條件中有兩個範圍列則沒法全用到索引。
1 如下是代碼片斷: 2 EXPLAIN SELECT * FROM employees.titles 3 WHERE emp_no<'10010' 4 AND title='Senior Engineer' 5 AND from_date BETWEEN '1986-01-01' AND '1986-12-31'; 6 +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ 7 | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 8 +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ 9 | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 4 | NULL | 16 | Using where | 10 +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
能夠看到索引對第二個範圍索引無能爲力。這裏特別要說明MySQL一個有意思的地方,那就是僅用explain可能沒法區分範圍索引和多值匹配,由於在type中這二者都顯示爲range。同時,用了「between」並不意味着就是範圍查詢,例以下面的查詢:
1 如下是代碼片斷: 2 EXPLAIN SELECT * FROM employees.titles 3 WHERE emp_no BETWEEN '10001' AND '10010' 4 AND title='Senior Engineer' 5 AND from_date BETWEEN '1986-01-01' AND '1986-12-31'; 6 +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ 7 | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 8 +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ 9 | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 59 | NULL | 16 | Using where | 10 +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
看起來是用了兩個範圍查詢,但做用於emp_no上的「BETWEEN」實際上至關於「IN」,也就是說emp_no實際是多值精確匹配。能夠看到這個查詢用到了索引所有三個列。所以在MySQL中要謹慎地區分多值匹配和範圍匹配,不然會對MySQL的行爲產生困惑。
1 如下是代碼片斷: 2 EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND left(title, 6)='Senior'; 3 +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+ 4 | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 5 +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+ 6 | 1 | SIMPLE | titles | ref | PRIMARY | PRIMARY | 4 | const | 1 | Using where | 7 +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+
雖然這個查詢和狀況五中功能相同,可是因爲使用了函數left,則沒法爲title列應用索引,而狀況五中用LIKE則能夠。再如:
1 如下是代碼片斷: 2 EXPLAIN SELECT * FROM employees.titles WHERE emp_no - 1='10000'; 3 +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+ 4 | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 5 +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+ 6 | 1 | SIMPLE | titles | ALL | NULL | NULL | NULL | NULL | 443308 | Using where | 7 +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
顯然這個查詢等價於查詢emp_no爲10001的函數,可是因爲查詢條件是一個表達式,MySQL沒法爲其使用索引。看來MySQL尚未智能到自動優化常量表達式的程度,所以在寫查詢語句時儘可能避免表達式出如今查詢中,而是先手工私下代數運算,轉換爲無表達式的查詢語句。
經過一個實際生產環境中的數據存取需求,分析如何設計此存儲結構,如何操縱存儲的數據,以及如何使操做的成本或代價更低,系統開銷最小。同時,讓更多初學者明白數據存儲的表上索引是如何一個思路組織起來的,但願起到一個參考模板的價值做用。
1.測試用例描述
測試用例爲B2C領域,一張用於存儲用戶選購物品而生成的產品訂單信息表,不過去掉一些其餘字段,以便用於測試,其表中的數據項也不特別描述,字段意思見表:
1 如下是代碼片斷: 2 SELECT count(DISTINCT(title))/count(*) AS Selectivity FROM employees.titles; 3 +-------------+ 4 | Selectivity | 5 +-------------+ 6 | 0.0000 | 7 +-------------+
其中,主鍵信息:PRIMARY KEY(order_id,`goods_id`),爲什麼主鍵索引索引字段的順序爲:order_id,`goods_id`,而不是: `goods_id`, order_id呢?緣由很簡單,goods_id在訂單信息表中的重複率會比order_id高,也即order_id的篩選率更高,能夠減小掃描索引記錄個數,從而達到更高的效率,同時,下面即將會列出的SQL也告訴咱們,有部分SQL語句的WHERE字句中只出現order_id字段,爲此更加堅決咱們必須把字段:order_id做爲聯合主鍵索引的頭部,`goods_id`爲聯合主鍵索引的尾部。
數據存儲表設計的小結:
設計用於存儲數據的表結構,首先要知道有哪些數據項,也即行內常說的數據流,以及各個數據項的屬性,好比存儲的數據類型、值域範圍及長度、數據完整性等要求,從而肯定數據項的屬性定義。存儲的數據項信息肯定以後,至少進行以下三步分析:
● 首先,肯定哪些數據項或組合,能夠做爲記錄的惟一性標誌;
● 其次,要肯定對數據記錄有哪些操做,每一個操做的頻率如何,對網站等類型應用,還須要區分前臺操做和後臺操做,也即分外部用戶的操做,仍是內部用戶的操做;
● 最後,對做爲數據記錄操做的條件部分的數據項,分析其數據項的篩選率如何,也即數據項不一樣值佔總數據記錄數的比例關心,比例越接近1則是篩選率越好,以及各個值得分佈率;
綜上所述,再讓數據修改性操做優先級別高於只讀性操做,就能夠建立一個知足要求且性能較好的索引組織結構。
數據的存取設計,就涉及一塊很是重要的知識: 關係數據庫的基礎知識和關係數據理論的範式。對於範式的知識點,特別解釋下,建議學到BCNF範式爲止,1NF、2NF、3NF和BCNF之間的差異,各自規避的問題、存在的缺陷都要一清二楚,可是在真實的工做環境中,不要任何存取設計都想向範式靠,用一句佛語準確點表達:空便是色,色便是空。
轉自:http://database.ctocio.com.cn/257/12120257.shtml
多列索引
MySQL單列索引是咱們使用MySQL數據庫中常常會見到的,MySQL單列索引和組合索引的區別可能有不少人還不是十分的瞭解,下面就爲您分析二者的主要區別,供您參考學習。
爲了形象地對比二者,再建一個表:
1 CREATE TABLE myIndex ( i_testID INT NOT NULL AUTO_INCREMENT, 2 vc_Name VARCHAR(50) NOT NULL, 3 vc_City VARCHAR(50) NOT NULL, i_Age INT NOT NULL, i_SchoolID INT NOT NULL, 4 PRIMARY KEY (i_testID) );
在這 10000 條記錄裏面 7 上 8 下地分佈了 5 條 vc_Name="erquan" 的記錄,只不過 city,age,school 的組合各不相同。
來看這條query:
1 SELECT i_testID FROM myIndex WHERE vc_Name='erquan' AND vc_City='鄭州' AND i_Age=25;
首先考慮建MySQL單列索引:
在vc_Name列上創建了索引。執行 T-SQL 時,MYSQL 很快將目標鎖定在了vc_Name=erquan 的 5 條記錄上,取出來放到一中間結果集。在這個結果集裏,先排除掉 vc_City 不等於"鄭州"的記錄,再排除 i_Age 不等於 25 的記錄,最後篩選出惟一的符合條件的記錄。
雖然在 vc_Name 上創建了索引,查詢時MYSQL不用掃描整張表,效率有所提升,但離咱們的要求還有必定的距離。一樣的,在 vc_City 和 i_Age 分別創建的MySQL單列索引的效率類似。
爲了進一步榨取 MySQL 的效率,就要考慮創建組合索引。就是將 vc_Name,vc_City,i_Age 建到一個索引裏:
1 ALTER TABLE myIndex ADD INDEX name_city_age (vc_Name(10),vc_City,i_Age);
建表時,vc_Name 長度爲 50,這裏爲何用 10 呢?由於通常狀況下名字的長度不會超過 10,這樣會加速索引查詢速度,還會減小索引文件的大小,提升 INSERT 的更新速度。
執行 T-SQL 時,MySQL 無須掃描任何記錄就到找到惟一的記錄。
確定有人要問了,若是分別在 vc_Name,vc_City,i_Age 上創建單列索引,讓該表有 3 個單列索引,查詢時和上述的組合索引效率同樣嗎?大不同,遠遠低於咱們的組合索引。雖然此時有了三個索引,但 MySQL 只能用到其中的那個它認爲彷佛是最有效率的單列索引。
創建這樣的組合索引,實際上是至關於分別創建了:vc_Name,vc_City,i_Age vc_Name,vc_City vc_Name
這樣的三個組合索引!爲何沒有 vc_City,i_Age 等這樣的組合索引呢?這是由於 mysql 組合索引「最左前綴」的結果。簡單的理解就是隻從最左面的開始組合。並非只要包含這三列的查詢都會用到該組合索引,下面的幾個query會用到:
1 SELECT * FROM myIndex WHREE vc_Name="erquan" AND vc_City="鄭州" 2 SELECT * FROM myIndex WHREE vc_Name="erquan"
而下面幾個則不會用到:
1 SELECT * FROM myIndex WHREE i_Age=20 AND vc_City="鄭州" 2 SELECT * FROM myIndex WHREE vc_City="鄭州"
使用索引列的查詢條件優化: 1.對於多列索引(組合索引),必須有索引中的最左列。 index_a_b_c(a,b,c)這個組合索引能夠被使用的條件是a/ab/ac(同單a)/abc 2.對於使用like的查詢,後面若是是常量而且只有%號不在第一個字符,索引纔可能被使用。 模糊查詢遵照最左固定原則,模糊查詢的首部不能是% 3.若是對大文本進行搜索,應該使用全文索引。 InnoDB不支持全文索引,MyISAM支持,因此InnoDB表儘可能不要使用like ‘%...%’。 4.若是列名是索引,使用 index_column is null將使用索引。Oracle是不行的。 5.若是mysql估計使用索引比全表掃描更慢,不會使用索引。 能夠檢查統計當作條件的索引鍵值是否超過整個表數據的20%,若是超過就不會使用索引。 6.若是使用memory/head表而且where條件中不使用」=」進行索引列,那麼不會用到索引。Head表只有在」=」的時候纔會使用索引。 7.用or分割開的條件,若是or前的條件中的列有索引,然後面列中沒有索引,那麼涉及到的索引都不會被用到。 8.若是列是字符串,那麼必定要在where條件中把字符串常量的值用引號引發來,不然不能走索引。 mysql默認把輸入的常量值進行轉換之後才進行檢索 9.通過普通運算或函數運算後的索引字段不能使用索引 10.不等於操做不能使用索引,<>、not in等 11.Order by 優化:某些狀況下,mysql可使用一個索引知足order by,而不須要額外的排序。Where條件與order by 使用相同的索引,而且order by的順序和索引順序相同,而且order by的字段都是升序或者都是降序。 SELECT * FROM t1 ORDER BY key_part1,key_part2,... ; SELECT * FROM t1 WHERE key_part1=1 ORDER BY key_part1 DESC, key_part2 DESC; SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 DESC; 可是如下狀況不使用索引: SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC ; --order by 的字段混合 ASC 和 DESC SELECT * FROM t1 WHERE key2=constant ORDER BY key1 ; -- 用於查詢行的關鍵字與 ORDER BY 中所使用的不相同 SELECT * FROM t1 ORDER BY key1, key2 ; -- 對不一樣的關鍵字使用 ORDER BY