博文首先說明索引的分類及建立,而後會涉及到索引的可用性選擇以及索引的優化。html
索引是什麼?先說建立索引的目的,建立索引是爲提升對數據的查詢速度。在字典的目錄中,咱們能夠很快找到某個字的位置,索引的做用就是相似於目錄,是爲了針對select操做而存在的。mysql
【索引是建立在表上,是對數據庫表中一列或多列的值進行排序的一種結構。索引能夠提升查詢速度。】sql
就像在字典上建立索引會增長字典的厚度同樣,數據庫的索引也是有缺點的,在文章的後面會說明。數據庫
索引有兩種存儲類型,B型樹索引和Hash索引。innoDB和MyISAM存儲引擎支持B型樹索引,memory存儲引擎二者都支持。默認是B型樹索引。json
【本片博文若是沒有特別說明,建立的都是B型樹索引(用的最多)】服務器
在建立索引時,不附加任何限制條件。這類索引能夠建立在任何數據類型中,值是否惟一和非空有自己的完整性約束條件決定。架構
索引的建立能夠在建立表時建立,也能夠在建表以後建立。ide
#1.建立表時建立索引
CREATE TABLE tb1( id int, name varchar(20), INDEX id_index (id DESC) ) #index|key 做爲索引的標識, id_index爲索引名(能夠不指定會有默認的),後面必須加上一個括號,括號裏建立索引的字段,最後的DESC表示倒序,ASC表示正序,默認正序!
#2.建立表以後添加索引,有兩種方法以下:
第一種:使用create語句
CREATE 【UNIQUE|FULLTEXT|SPATIAL】INNEX 索引名 ON TABLE_NAME (屬性名 [(長度)]);
第二種:使用alter語句。
ALTER TABLE TABLE_NAME ADD 【UNIQUE|FULLTEXT|SPATIAL】 INNEX 索引名 (屬性名[(長度)]);
##須要注意的是在char類型的字段上建立索引時,能夠指定在當前字段的前幾個字符來建立索引。
#以下:在上面的表的name字段的前5個字符建立索引。(這裏的索引只是爲了練習)
CREATE INDEX index_name ON tb1 (name(5) DESC);
mysql> SHOW CREATE TABLE tb1\G
*************************** 1. row ***************************
Table: tb1
Create Table: CREATE TABLE `tb1` (
`id` int(11) DEFAULT NULL,
`name` varchar(20) DEFAULT NULL,
KEY `index_name` (`name`(5)) #建立的以name字段的前5個字符爲索引
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec
使用UNIQUE參數能夠設置索引爲惟一性索引。限制該索引值必須是惟一的。主鍵是一種特殊的惟一性索引。性能
在上面的表中,id字段通常爲惟一性索引,咱們在id字段上建立惟一性索引。優化
ALTER TABLE tb1 ADD UNIQUE INDEX index_id ( id ASC ); #在已經建立的表上添加惟一性索引
使用fulltext參數能夠設置索引爲全文索引。全文索引只能建立在CHAR, VARCHAR,TEXT類型的字段上。查詢數據量較大的字符串類型字段時,使用全文索引能夠提升查詢速度。
#在表中添加一個text字段,而後在字段上建立全文索引 ALTER TABLE tb1 ADD info text; CREATE FULLTEXT INDEX index_info ON tb1 ( info )
在表中一個字段上建立的索引。以上的建立的三個索引均爲單列索引。
多列索引時在表的多個字段上建立一個索引。該索引指向建立時對應的多個字段,能夠經過這幾個字段進行查詢。可是,只有查詢條件中使用了這些字段的第一個字段時,索引纔會被引用。
CREATE INDEX name_index ON employees ( first_name, last_name ); #在employees表中建立一個雙列索引
須要注意的是,在多列索引時,在查詢時,只有第一個字段被引用,那麼這個索引纔會被使用。
#有以下表,在表中插入數據 CREATE TABLE tb2 ( a INT, b INT ); INSERT INTO tb2 VALUES ( 1, 2 ), ( 4, 3 ), ( 2, 1 ), ( 5, 9 ), ( 3, 4 ),
( 2, 4 ),
( 3, 1 ); CREATE INDEX test_index ON tb2 ( a, b );
#而後在表中建立一個複合索引如圖。
特別須要注意的是:建立索引以後對應的字段時邏輯有序的,而不是物理有序。
索引建立以後,表中的這些數據邏輯順序以下:
+------+------+
| a | b | #字段a是按照邏輯大小的順序排列的,可是字段b卻不是,
+------+------+ #所以在使用索引時,必須使用第一個字段才能夠在查詢中使用索引
| 1 | 2 |
| 2 | 1 |
| 2 | 4 |
| 3 | 1 |
| 3 | 4 |
| 4 | 3 |
| 5 | 9 |
+------+------+
空間索引的存儲引擎必須爲MyIsam。使用SPATIAL參數能夠設置索引爲空間索引。空間索引只能創建在空間類型上。MySQL中的空間數據類型包括GEOMETRY,POINT,LINESTRING和POLYGON等。(暫時沒用到,不詳細說明)
刪除索引可使用以下語句:
drop index 索引名 on 表名; mysql> drop index test_index on tb2; Query OK, 0 rows affected (0.02 sec) Records: 0 Duplicates: 0 Warnings: 0
索引爲什麼會提升數據查詢的效率?
(提升數據的查詢速度,最重要的是想辦法減小數據查詢時對磁盤的IO操做,而服務器的CPU運算基本都是盈餘的)
【待續】
建立一個索引,咱們須要去評估這個建立的是否合理?若是一個表的數據量不多,或者這個字段的值重複性比較多,那麼建立這個索引就沒有意義。在一張數據量比較大的表中,而且這個字段的重複性值不高,這時候咱們能夠建立索引。
咱們如何知道這個字段究竟有多少條不重複的數據?
MySQL給咱們提供了一個參數:Cardinality,這個值表示的是記錄不重複數據量的行數。
mysql> show index from employees\G *************************** 1. row *************************** Table: employees Non_unique: 0 Key_name: PRIMARY Seq_in_index: 1 Column_name: emp_no Collation: A Cardinality: 299290 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: Index_comment: *************************** 2. row *************************** Table: employees Non_unique: 1 Key_name: name_index Seq_in_index: 1 Column_name: first_name Collation: A Cardinality: 1288 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: Index_comment: *************************** 3. row *************************** Table: employees Non_unique: 1 Key_name: name_index Seq_in_index: 2 Column_name: last_name Collation: A Cardinality: 279473 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: Index_comment: 3 rows in set (0.00 sec) #各個字段解釋以下:
Table: 表名。
Non_unique:若是索引不能包含重複項則爲0,能夠則爲1.
Key_name:索引的名字。
Seq_in_index:當前字段在複合索引中是第幾個字段。(單列索引則爲1)
Column_name:字段名字。
Collation:列如何在索引中排序,值A表示升序。未排序則爲NULL。
Cardinality:利用抽樣法估計的當前字段中不重複的行數。
Sub_part:索引前綴,如果整個字段索引則值爲NULL,如果僅字符類型的前幾個字符索引,則顯示字符的數量。
Packed: 指示關鍵字如何被壓縮。若是沒有被壓縮,則爲NUL
Index_type:索引類型。(, ,, )
commecnt: Information about the index not described in its own column, such as if the index is disabled.
BTREEFULLTEXTHASHRTREEdisabled
Index_comment:建立索引時的一些說明信息。
#證實索引可行性的時候,咱們須要額外關注Cardinality這個數值,這個數值的更新能夠人爲的使用ANALYZE table(myisam存儲引擎須要使用 myisamchk -a)
在innodb存儲引擎中,Cardinality統計信息的更新發生在兩個操做中:INSERT,UPDATE。可是不是會在每次操做時,都會更新這個數值,innodb存儲引擎更新Cardinality值得策略爲:
第一種策略爲自上次統計Cardinality信息後,表中1/16的數據已經發生變化,這時須要更新Cardinality信息。第二種:若是對錶中某一行的數據頻繁的更新,那麼表中的數據並無增長,
發生變化的仍是這一行數據,那麼第一種策略就沒法生效。所以在innodb存儲引擎內部有一個計數器stat_modified_counter,用來表示發生變化的次數,當更新的值大於指定的值時,
就會更新Cardinality的數值。
innodb打開某些INFORMATION_SCHEMA表,或者使用show table status和show index,抑或在MySQL客戶端開啓自動補全功能的時候都會觸發索引統計信息的更新,若是服務器上有大量的數據,這可能就是個很嚴重的問題,尤爲是當I/O比較慢的時候,客戶端或者監控程序觸發索引信息採樣更新時會致使大量的鎖,並給服務器帶來不少額外的壓力。所以MySQL內部使用了一個參數來關閉自動觸發的索引採樣。
mysql> show variables like "innodb_stats_on_metadata"; +--------------------------+-------+ | Variable_name | Value | +--------------------------+-------+ | innodb_stats_on_metadata | OFF | +--------------------------+-------+ 1 row in set (0.00 sec) mysql>
那麼在MySQL內部,是怎麼樣經過採樣計算card'inality值的?默認innodb存儲引擎對8個葉子節點進行採樣處理。
mysql> show variables like "innodb_stats_sample_pages"; #默認的採樣的8個葉子節點。 +---------------------------+-------+ | Variable_name | Value | +---------------------------+-------+ | innodb_stats_sample_pages | 8 | +---------------------------+-------+ 1 row in set (0.01 sec)
#採樣過程以下:
#隨機採樣得到的8個頁是隨機的,所以每次採樣獲得的cardinality值多是不一樣的。
innodb_stats_sample_pages參數用來控制隨機採樣葉的多少,而innodb_stats_method用來判斷如何對待索引中出現的null值激勵。該值默認值爲nulls_equal,表示將null值視爲相等的記錄。
其有效值還有null_unequal,null_ignored,分別表示將null值記錄視爲不一樣的記錄和忽略null值的記錄。【注意三個值的區別,視爲相等的記錄,視爲不一樣的記錄,忽略null值】
與cardinality值相關的還有以下的幾個參數:
innodb_stats_persistent: 是否將命令analyze table計算獲得的cardinality值存放到磁盤上。如果,則這樣作的好處是能夠減小從新計算每一個索引的cardinality值。
例如當MySQL數據庫重啓時。此外,用戶也能夠經過命令create table和alter table的選項stats_persistent來對每張表進行控制。
innodb_stats_on_metadata: 當命令show table status, show index以及訪問information_schema架構下的表tables和statistics使,是否須要從新計算cardinality值,默認是OFF。
innodb_stats_persistent_sample_pages:若參數innodb_stats_persistent設置爲ON,該參數表示analyze table更新cardinality值時的每次採樣頁的數量。默認是20.
innodb_stats_transient_sample_pages: 這個參數用來取代以前版本的innodb_stats_sample_pages參數,表示每次採樣頁的數量。默認是8.
查看錶的一些基本信息:
mysql> show table status like "employees"\G *************************** 1. row *************************** Name: employees Engine: InnoDB Version: 10 Row_format: Dynamic #表格式 Rows: 299290 #錶行數,對於myisam表這個值時精確的,對innodb這個值時估算的,可使用select count(*) from tbname.來精確計算 Avg_row_length: 50 #表的評價每行的長度 Data_length: 15220736 #對myisam表,是數據文件的長度,以字節爲單位。對innodb表,是爲聚簇索引分配的大體內存量,以字節爲單位。 Max_data_length: 0 #對於, 是數據文件的最大長度。這是在給定數據指針大小的狀況下能夠存儲在表中的數據的總字節數,未使用innodb。 Index_length: 0 Data_free: 2097152 # Auto_increment: NULL #下一個值 Create_time: 2018-10-07 16:54:40 Update_time: NULL Check_time: NULL Collation: latin1_swedish_ci #排序規則 Checksum: NULL #實時校驗和 Create_options: Comment: 1 row in set (0.01 sec)
字段的詳細解釋能夠查看:https://dev.mysql.com/doc/refman/5.7/en/show-table-status.html
在這裏咱們暫時只用到: Rows
#information_schema:這個庫中tables表和show table status輸出的內容是同樣。
MyISAMMax_data_lengthAUTO_INCREMENT
可選擇性計算: Cardinality/ table_rows,數值越接近1,則說明索引的可選擇性越高。
查看數據庫中指定庫中表的索引的可選擇性,可使用以下代碼:
USE information_schema; SELECT t.table_schema, t.table_name, a.index_name, t.table_rows, a.COLUMN_NAME, a.cardinality, a.cardinality / t.table_rows AS seletivity FROM TABLES t INNER JOIN ( SELECT s.table_schema, s.table_name, s.index_name, b.COLUMN_NAME, s.cardinality FROM statistics s INNER JOIN ( SELECT table_schema, table_name, index_name, GROUP_CONCAT(COLUMN_NAME) AS COLUMN_NAME, max(seq_in_index) AS seq_in_index FROM STATISTICS WHERE table_schema = "employees" GROUP BY table_schema, table_name, index_name ) b ON s.table_schema = b.table_schema AND s.table_name = b.table_name AND s.seq_in_index = b.seq_in_index ) a ON t.table_schema = a.table_schema AND t.table_name = a.table_name ORDER BY seletivity
結果以下:
建立索引以後,咱們可使用explain語句查看select查詢是否使用了索引。
mysql> EXPLAIN SELECT * from employees LIMIT 1; +----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------+ | 1 | SIMPLE | employees | NULL | ALL | NULL | NULL | NULL | NULL | 299246 | 100.00 | NULL | +----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------+ 1 row in set, 1 warning (0.00 sec)
#explain語句各個字段解釋以下:
id: 表示當前select語句的編號,該值可能爲空,若是行聯合了其餘行的結果;在這種狀況下table列顯示的是,引用的行的並集。
select_type: 這個值有不少,暫時能夠先記如下幾個:
table: 查詢的表名
partitions:顯示查詢使用的分區,若爲NULL則未使用分區。
type:表示表的鏈接類型,有以下取值:
possible_keys:表示查詢中可能使用的索引;若是備選的數量大於3那說明已經太多了,由於太多會致使選擇索引而損耗性能, 因此建表時字段最好精簡,同時也要創建聯合索引,避免無效的單列索引;
key: 查詢實際使用的索引(不太準確,能夠查閱官方文檔)。
key_len:索引的長度
ref: REF列顯示哪些列或常量與鍵列中所命名的索引進行比較,以從表中選擇行。
rows: 查詢掃描的行數。
filtered:表示按條件過濾錶行的百分比,最大爲100表示100%。
Extra: 表示查詢額外的附加信息說明。
上面的expalin語句也能夠換位desc命令。
除了直接使用explain命令以外,MySQL5.7還支持json格式的輸出,
mysql> EXPLAIN format=json SELECT * from employees LIMIT 1\G *************************** 1. row *************************** EXPLAIN: { "query_block": { "select_id": 1, "cost_info": { "query_cost": "60778.20" }, "table": { "table_name": "employees", "access_type": "ALL", "rows_examined_per_scan": 299246, "rows_produced_per_join": 299246, "filtered": "100.00", "cost_info": { "read_cost": "929.00", "eval_cost": "59849.20", "prefix_cost": "60778.20", "data_read_per_join": "13M" }, "used_columns": [ "emp_no", "birth_date", "first_name", "last_name", "gender", "hire_date" ] } } } 1 row in set, 1 warning (0.00 sec) mysql>