這是《高性能 MySQL(第三版)》第五章的讀書筆記。mysql
索引在 MySQL 中也叫鍵(Key),是存儲引擎用於快速找到記錄的一種數據結構。web
表的數據量增大時,索引對良好的性能很是關鍵。索引是優化查詢性能的最有效的手段。sql
MySQL 中,存儲引擎先在索引中找到對應值,而後根據匹配的索引記錄找到對應的數據行。數據庫
mysql> SELECT * FROM blog.user WHERE user_id = 5;
若是 user_id 列上建有索引,MySQL 將使用該索引找到 user_id 爲 5 的行。MySQL 先在索引上按值進行查找,而後返回全部包含該值的數據行。緩存
索引能夠包含一個或多個列的值。若是包含多個列,MySQL 只能高效使用最左側的前綴列。bash
B-Tree 索引使用 B-Tree 數據結構來存儲數據。服務器
MySQL 的默認索引類型,大多數存儲引擎都支持,只是存儲結構會有所差別。例如 InnoDB 使用 B+Tree。數據結構
存儲引擎以不一樣方式使用 B-Tree 索引,性能也不一樣。MyISAM 使用前綴壓縮技術,使索引更小,而 InnoDB 則按照原數據格式進行存儲。MyISAM 索引經過數據的物理位置引用被索引的行,而 InnoDB 則根據主鍵引用被索引的行。svg
B-Tree 對索引列是順序組織存儲的,適合查找某個範圍內的數據。對於基於文本域的索引樹,索引按字母排序。函數
對於下面的數據表:
CREATE TABLE people ( last_name VARCHAR(50) NOT NULL, first_name VARCHAR(50) NOT NULL, dob date NOT NULL, gender ENUM('m', 'f') NOT NULL, key(last_name, first_name, dob) );
INSERT INTO people VALUES ('Allen', 'Cuba', '1960-01-01', 'f'), ('Akroyd', 'Debbie', '1990-03-01', 'f'), ('Akroyd', 'Kristin', '1978-03-01', 'f'), ('Allen', 'Kristin', '1990-03-01', 'f'), ('Allen', 'Kim', '1920-03-01', 'f'), ('Allen', 'Merry', '1930-03-01', 'f'), ('Barry', 'Julia', '1990-03-01', 'f'), ('Bssia', 'Vivew', '1990-03-01', 'f'), ('Bssia', 'Vivew', '1960-03-01', 'f') ;
表中每行數據的索引中包含了 last_name, first_name, dob 三列。對應的內存結構是:
B-Tree 索引適合的查詢類型有:
SELECT * FROM people WHERE last_name = 'Allen' AND first_name = 'Cuba' AND dob = '1960-01-01';
SELECT * FROM people WHERE last_name = 'Allen';
SELECT * FROM people WHERE last_name LIKE 'A%';
SELECT * FROM people WHERE last_name BETWEEN 'Allen' AND 'Eine';
SELECT * FROM people WHERE last_name = 'Allen' AND first_name LIKE 'K%';
SELECT last_name FROM people WHERE last_name LIKE 'A%';
B-Tree 索引的限制:
SELECT * FROM people WHERE last_name LIKE '%en'; <----------沒用到索引中最左列的前綴
SELECT * FROM people WHERE first_name LIKE 'K%'; <----------沒用到索引中的最左列
SELECT * FROM people WHERE dob = '1960-01-01'; <----------沒用到索引中的最左列
SELECT * FROM people WHERE last_name= 'Allen' and first_name LIKE 'K%' and dob = '1920-03-01';
哈希索引基於哈希表實現,只有精確匹配索引全部列的查詢纔會有效。對於每一行數據,存儲引擎都會對全部的索引列計算一個哈希碼。哈希索引將全部的哈希碼存儲在索引中,同時在哈希表中保存指向每一個數據行的指針。
MySQL 中只有 Memory 引擎顯式支持哈希索引,且支持非惟一哈希索引。若是多個列的哈希值相同,索引會以鏈表的方式存放多個記錄指針到同一個哈希條目中。
對於下面的數據表:
CREATE TABLE hash_test ( fname VARCHAR(50) NOT NULL, Lname VARCHAR(50) NOT NULL, KEY USING HASH(fname) ) ENGINE=MEMORY;
INSERT INTO hash_test VALUES ('Arjen', 'Lentz'), ('Baron', 'Shwora'), ('Peter', 'Zaier'), ('Vadim', 'Tkachen') ;
假設使用哈希函數 f(),其返回值以下:
f('Arjen') = 2323
f('Baron') = 7837
f('Peter') = 8784
f('Vadim') = 2458
對應的哈希索引的數據結構以下:
Slot | Value |
---|---|
2323 | 指向第 1 行的指針 |
2458 | 指向第 4 行的指針 |
7837 | 指向第 2 行的指針 |
8784 | 指向第 3 行的指針 |
其中每一個 Slot 的編號是順序的,可是數據行不是。對於下面的查詢:
SELECT * FROM hash_test WHERE fname = 'Peter';
MySQL 會首先計算 Peter 的哈希值,並用該值尋找對應的記錄指針,最後對比字段數據。哈希值爲 8784,從而找到指向第 3 行的指針,而後對比這一行的字段數據是不是 Peter。
哈希索引的限制:
=
、IN()
、<=>
。不支持範圍查詢。哈希索引只適用於一些特定的場合。例如星型 schema,須要關聯不少查找表,哈希索引適合查找表的需求。
InnoDB 引擎有一個特殊功能「自適應哈希索引(adaptive hash index)。當 InnoDB 引擎注意到某些索引值被使用的很是頻繁時,它會在內存中基於 B-Tree 索引之上再建立一個哈希索引。這個功能徹底自動,用戶沒法控制,可是能夠關閉。
若是存儲引擎不支持哈希索引,能夠參考 InnoDB,在 B-Tree 基礎上建立一個僞哈希索引。這個索引仍是使用 B-Tree 進行查找,可是使用哈希值而不是鍵自己進行索引查找。須要在查詢的 WHERE 字句中手動指定使用哈希函數。
例如,須要對某個很長的 URL 字段進行索引。若是使用 B-Tree 來存儲,須要消耗大量存儲空間。可是插入、查詢等語句簡單:
SELECT * FROM url WHERE url = "http://www.baidu.com";
如今,刪除原來 url 列上的索引,增長一個被索引的 url_crc 列,使用 CRC32 作哈希,查詢語句以下:
SELECT * FROM url WHERE url = "http://www.baidu.com" AND url_crc = CRC32("http://www.baidu.com");
MySQL 優化器會使用這個選擇性很高而體積很小的基於 url_crc 的索引來完成查詢,高效。若是對完整的 URL 字符串作索引,會很是慢。
哈希值能夠手動維護,也能夠經過觸發器維護。經過觸發器在插入和更新時維護 url_crc 列的示例:
CREATE TABLE pseudo_hash ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, url VARCHAR(255) NOT NULL, url_crc INT UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY(id) );
建立觸發器,先經過 DELIMITER 臨時修改分隔符,這樣就能夠在觸發器定義中使用分號:
DELIMITER //
CREATE TRIGGER pseudohash_crc_ins BEFORE INSERT ON pseudo_hash FOR EACH ROW BEGIN SET NEW.url_crc = CRC32(NEW.url);
END;
//
CREATE TRIGGER pseudohash_crc_upd BEFORE UPDATE ON pseudo_hash FOR EACH ROW BEGIN SET NEW.url_crc = CRC32(NEW.url);
END;
//
DELIMITER ;
驗證觸發器可否維護哈希索引:
mysql> INSERT INTO pseudo_hash (url) VALUES('http://www.mysql.com') ; Query OK, 1 row affected (0.00 sec) mysql> SELECT * FROM pseudo_hash; +----+----------------------+------------+
| id | url | url_crc | +----+----------------------+------------+
| 1 | http://www.mysql.com | 1560514994 | +----+----------------------+------------+
1 row in set (0.00 sec)
mysql> UPDATE pseudo_hash SET url='http://www.baidu.com' WHERE id=1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> SELECT * FROM pseudo_hash; +----+----------------------+------------+
| id | url | url_crc | +----+----------------------+------------+
| 1 | http://www.baidu.com | 3500265894 | +----+----------------------+------------+
1 row in set (0.00 sec)
注意,這裏的哈希函數儘可能使用 CRC32(),若是使用 MD5() 或 SHA1(),儘管碰撞機率下降了,可是哈希值很是長,且查詢速度慢。
mysql> SELECT CRC32('http://www.baidu.com'); +-------------------------------+
| CRC32('http://www.baidu.com') | +-------------------------------+
| 3500265894 | <---------------INT 類型,32 位 +-------------------------------+
1 row in set (0.00 sec)
mysql> SELECT MD5('http://www.baidu.com'); +----------------------------------+
| MD5('http://www.baidu.com') | +----------------------------------+
| bfa89e563d9509fbc5c6503dd50faf2e | <---------------String 類型,128 位 +----------------------------------+
1 row in set (0.00 sec)
mysql> SELECT SHA1('http://www.baidu.com'); +------------------------------------------+
| SHA1('http://www.baidu.com') | +------------------------------------------+
| 633a42441e296c9004a78abe0b2ee3b37559d32f | <---------------String 類型,160 位 +------------------------------------------+
1 row in set (0.00 sec)
若是數據量很是大,CRC32() 出現大量衝突時,能夠本身實現返回 64 位整數的哈希函數。簡單示例以下:
mysql> SELECT CONV(RIGHT(MD5('http://www.baidu.com'), 16), 16, 10) AS HASH64; +----------------------+
| HASH64 | +----------------------+
| 14251166297358315310 | +----------------------+
1 row in set (0.00 sec)
MD5() 函數返回 32 位 16 進制的哈希值,RIGHT() 函數返回哈希值最後的 16 位字符。CONV() 函數將字符串從 16 進制轉爲 10 進制。
使用哈希索引進行查詢時,必須在 WHERE 子句中包含哈希值和對應列值,即便發生衝突也能夠正常工做:
SELECT * FROM url WHERE url = "http://www.baidu.com" AND url_crc = CRC32("http://www.baidu.com");
CRC32() 返回的是 32 位整數,當索引有 93000 條記錄時出現衝突的機率是 1%。將 /usr/share/dict/words
中的詞導入數據表,會有 98569 行。WHERE 子句中只包含哈希值時,衝突時返回多行數據而不是一行數據。
用於地理數據存儲。空間索引會從全部維度來索引數據,可使用任意維度來組合查詢。MySQL 的 GIS 支持並不完善,可使用 PostgreSQL 的 PostGIS。
查找文本中的關鍵詞,而不是直接比較索引中的值。相似於搜索引擎,而不是簡單的 WHERE 條件匹配。
在同一個列上能夠同時建立全文索引和基於值的 B-Tree 索引。全文索引適用於 MATCH AGAINST 操做,而不是普通的 WHERE 條件操做。
經過索引,服務器能夠快速定位到表的位置。
對於 B-Tree 索引,按照順序存儲數據,MySQL 能夠進行 ORDER BY 和 GROUP BY 操做。由於數據有序存儲,因此 B-Tree 也會將相關的列值存儲在一塊兒。若是要查詢的字段包含在索引中,則只使用索引就能完成查詢。索引優勢:
數據量小的表,一般全局掃描更高效。中大型表,使用索引更有效。對於特大型表,創建和使用索引的代價也隨之增加,須要使用其餘技術,例如分區。
查詢語句中,若是列不是獨立的,MySQL 就不會使用索引。列不是獨立的,指的是列是表達式的一部分,或函數參數。例如:
SELECT ... WHERE id + 1 = 5;
SELECT ... WHERE TO_DAYS(CURRENT_DATE) - TO_DAYS(date_col) <= 10;
在很長的字符列上使用索引時,會使索引大且慢。可使用前面的模擬哈希索引。
也能夠只索引開始的部分字符。對於 BLOB、TEXT 或很長的 VARCHAR 類型的列,必須使用前綴索引。須要選擇足夠長的前綴以保證較高的選擇性(防止重複),同時不能太長以節約空間。
前綴索引的優缺點:
在示例數據庫 sakila 中沒有合適的例子,須要從表 city 中生成一個示例表:
CREATE TABLE sakila.city_demo(city VARCHAR(50) NOT NULL);
INSERT INTO sakila.city_demo(city) SELECT city FROM sakila.city;
INSERT INTO sakila.city_demo(city) SELECT city FROM sakila.city_demo; -- <---- 這一行重複屢次
UPDATE sakila.city_demo SET city = (SELECT city FROM sakila.city ORDER BY RAND() LIMIT 1);
例以下面的例子:
MariaDB [sakila]> CREATE TABLE sakila.city_demo(city VARCHAR(50) NOT NULL);
Query OK, 0 rows affected (0.196 sec)
MariaDB [sakila]> INSERT INTO sakila.city_demo(city) SELECT city FROM sakila.city;
Query OK, 600 rows affected (0.032 sec)
Records: 600 Duplicates: 0 Warnings: 0
MariaDB [sakila]> INSERT INTO sakila.city_demo(city) SELECT city FROM sakila.city_demo;
Query OK, 600 rows affected (0.004 sec)
Records: 600 Duplicates: 0 Warnings: 0
MariaDB [sakila]> INSERT INTO sakila.city_demo(city) SELECT city FROM sakila.city_demo;
Query OK, 1200 rows affected (0.007 sec)
Records: 1200 Duplicates: 0 Warnings: 0
...
MariaDB [sakila]> INSERT INTO sakila.city_demo(city) SELECT city FROM sakila.city_demo;
Query OK, 76800 rows affected (0.277 sec)
Records: 76800 Duplicates: 0 Warnings: 0
MariaDB [sakila]> UPDATE sakila.city_demo SET city = (SELECT city FROM sakila.city ORDER BY RAND() LIMIT 1);
Query OK, 153358 rows affected (1 min 6.620 sec)
Rows matched: 153600 Changed: 153358 Warnings: 0
能夠經過計算完整列的選擇性來計算合適的前綴長度,使前綴的選擇性接近於完整列的選擇性。計算完整列的選擇性:
MariaDB [sakila]> SELECT COUNT(DISTINCT city)/COUNT(*) FROM sakila.city_demo; +-------------------------------+ | COUNT(DISTINCT city)/COUNT(*) |
+-------------------------------+
| 0.0039 |
+-------------------------------+
1 row in set (0.119 sec)
計算不一樣前綴長度的選擇性:
MariaDB [sakila]> SELECT COUNT(DISTINCT LEFT(city, 3))/COUNT(*) AS sel3, -> COUNT(DISTINCT LEFT(city, 4))/COUNT(*) AS sel4,
-> COUNT(DISTINCT LEFT(city, 5))/COUNT(*) AS sel5,
-> COUNT(DISTINCT LEFT(city, 6))/COUNT(*) AS sel6,
-> COUNT(DISTINCT LEFT(city, 7))/COUNT(*) AS sel7
-> FROM sakila.city_demo; +--------+--------+--------+--------+--------+
| sel3 | sel4 | sel5 | sel6 | sel7 | +--------+--------+--------+--------+--------+
| 0.0030 | 0.0037 | 0.0038 | 0.0039 | 0.0039 | +--------+--------+--------+--------+--------+
1 row in set (0.312 sec)
前綴長度達到 7 時,選擇性提高的幅度基本穩定。
MariaDB [sakila]> ALTER TABLE sakila.city_demo ADD KEY(city(7));
Query OK, 0 rows affected (3.350 sec)
Records: 0 Duplicates: 0 Warnings: 0
多列索引同時在多個列上創建一個索引,索引列的順序很重要。對於 AND 條件致使的索引相交時,最好使用一個包含全部相關列的多列索引,而不是多個獨立的單列索引。
最經常使用的規則:將選擇性最高的列放到索引最前列。
聚簇索引是一種數據存儲方式,而不是索引類型。InnoDB 的聚簇索引在同一個結構中保存了 B-Tree 索引和數據行。
表有聚簇索引時,數據行實際上存放在索引的葉子頁中。「聚簇」表示將數據行和相鄰的鍵值存儲在一塊兒。由於數據行只能存儲在一個地方,因此一個表只能有一個聚簇索引。
聚簇索引的優勢:
聚簇索引的缺點:
通常經過查詢的 WHERE 條件來建立合適的索引。可是設計優秀的索引應該考慮整個查詢。對於常用的某幾個列,能夠考慮添加覆蓋索引。
覆蓋索引:索引包含(覆蓋)全部須要查詢的字段的值。MySQL 只能用 B-Tree 索引作覆蓋索引。
覆蓋索引的優勢:
MySQL 有兩種方式生成有序結果:
掃描索引自己是很快的,只須要從一條索引記錄移動到緊接着的下一條記錄。但若是索引不能覆蓋查詢所需的所有列(例如 SELECT *
操做),就不得不每掃描一條索引記錄就回表查詢一次對應的行,這基本上都是隨機 I/O。按索引順序讀數據一般比順序地全表掃描速度慢,尤爲是 I/O 密集型工做負載。
MySQL 的索引能夠同時用於排序和查找行。
用索引(而不是排序操做)對結果排序的規則:
Sakila 示例數據庫的 rental 表在列(rental_date
, inventory_id
, customer_id
)上面建立了索引。
CREATE TABLE `rental` ( `rental_id` INT(11) NOT NULL AUTO_INCREMENT, `rental_date` DATETIME NOT NULL, `inventory_id` MEDIUMINT(8) UNSIGNED NOT NULL, `customer_id` SMALLINT(5) UNSIGNED NOT NULL, `return_date` DATETIME NULL DEFAULT NULL, `staff_id` TINYINT(3) UNSIGNED NOT NULL, `last_update` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`rental_id`), UNIQUE INDEX `rental_date` (`rental_date`, `inventory_id`, `customer_id`), INDEX `idx_fk_inventory_id` (`inventory_id`), INDEX `idx_fk_customer_id` (`customer_id`), INDEX `idx_fk_staff_id` (`staff_id`), CONSTRAINT `fk_rental_customer` FOREIGN KEY (`customer_id`) REFERENCES `customer` (`customer_id`) ON UPDATE CASCADE, CONSTRAINT `fk_rental_inventory` FOREIGN KEY (`inventory_id`) REFERENCES `inventory` (`inventory_id`) ON UPDATE CASCADE, CONSTRAINT `fk_rental_staff` FOREIGN KEY (`staff_id`) REFERENCES `staff` (`staff_id`) ON UPDATE CASCADE ) COLLATE='utf8_general_ci' ENGINE=InnoDB AUTO_INCREMENT=16050 ;
下面示例中,WHERE 子句將前導列指定爲常量。經過 rental_date 索引爲下面的查詢進行排序,從 EXPLAIN 中能夠看到沒有出現文件排序(filesort)操做:
mysql> EXPLAIN SELECT * FROM rental WHERE rental_date='2005-05-25' ORDER BY inventory_id, customer_id; +----+-------------+--------+------+---------------+-------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+------+---------------+-------------+---------+-------+------+-------------+
| 1 | SIMPLE | rental | ref | rental_date | rental_date | 8 | const | 1 | Using where | +----+-------------+--------+------+---------------+-------------+---------+-------+------+-------------+
1 row in set (0.00 sec)
MyISAM 使用前綴壓縮來減少索引的大小,將更多索引放入內存中。默認只壓縮字符串,能夠開啓對整數的壓縮。
壓縮後,磁盤空間佔用變爲以前的十分之一,但速度變慢。I/O 密集型應用能夠考慮壓縮,CPU 密集型應用就算了。
CREATE TABLE 時指定 PACK_KEYS 參數來控制索引壓縮方式。
能夠在同一個列上建立多個索引。MySQL 須要單獨維護重複的索引,且優化器在優化查詢的時候也須要逐個考慮,影響性能。
重複索引:在同一個列上按相同順序建立相同類型的索引。這是錯誤的用法。
冗餘索引:若是建立了索引(A,B),再建立索引(A)就是冗餘索引,由於這只是前一個索引的前綴索引。索引(A,B)也能夠當作索引(A)來使用(冗餘只是針對 B-Tree 索引而言)。再建立索引(B,A)或(B)都不是冗餘索引。另外,不一樣類型的索引(例如哈希索引或全文索引)也不會是 B-Tree 索引的冗餘索引,無論覆蓋哪一個列。通常不須要冗餘索引。
冗餘索引一般發生在添加新索引時,例如:
查找方法:在 MariaDB 中打開 userstates 服務器變量(默認是關閉的),讓服務器運行一段時間後,經過查詢 INFORMATION_SCHEMA.INDEX_STATISTICS 能夠查到每一個索引的使用頻率。
另外,可使用 Perconna Tollkit 中的 pt-index-usage 工具,讀取查詢日誌後對每條查詢進行 EXPLAIN 操做,而後打印關於索引和查詢的報告。
InnoDB 只有在訪問行的時候纔會對其加鎖,而索引能夠減小 InnoDB 訪問的行數,從而減小鎖的數量。索引可讓查詢鎖定更少的行。
下面示例返回 2-4 行數據,可是會鎖定 1-4 行的數據。由於 MySQL 爲該查詢選擇的計劃是索引範圍掃描,存儲引擎只接收了 WHERE 條件的第一部分,
mysql> SET AUTOCOMMIT=0;
Query OK, 0 rows affected (0.00 sec)
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT actor_id FROM sakila.actor WHERE actor_id < 5 AND actor_id <> 1 FOR UPDATE; +----------+
| actor_id | +----------+
| 2 |
| 3 |
| 4 | +----------+
3 rows in set (0.00 sec)
mysql> EXPLAIN SELECT actor_id FROM sakila.actor WHERE actor_id < 5 AND actor_id <> 1 FOR UPDATE; +----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+
| 1 | SIMPLE | actor | range | PRIMARY | PRIMARY | 2 | NULL | 3 | Using where; Using index | +----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT actor_id FROM sakila.actor WHERE actor_id < 5 FOR UPDATE; +----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+
| 1 | SIMPLE | actor | range | PRIMARY | PRIMARY | 2 | NULL | 4 | Using where; Using index | +----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
上面的例子中,Extra 列中出現了「Using WHERE」,表示 MySQL 服務器在存儲引擎返回行後再應用 WHERE 過濾條件。怎麼證實確實鎖定了第一行數據呢,新開一個鏈接,訪問這一行數據便可,會發現查詢一直是掛起狀態,直到上面窗口提交或回滾事務(留意總查詢時間):
mysql> SELECT actor_id FROM sakila.actor WHERE actor_id = 1 FOR UPDATE; +----------+
| actor_id | +----------+
| 1 | +----------+
1 row in set (45.64 sec)
網站用戶信息表具備不少列,例如國家、城市、地區、性別,須要支持這些特徵來搜索用戶。同時,須要根據評分、最後登陸時間等對用戶排序並限制結果。
首先須要考慮的是使用索引排序,仍是先檢索數據再排序。使用索引排序須要嚴格限制索引和查詢的設計。例如,若是但願用索引作根據評分的排序,則 WHERE 條件中的 age BETWEEN 18 AND 25 就沒法使用索引。若是使用某個索引進行範圍查詢,也就沒法再使用另外一個索引(或該索引的後續字段)進行排序了。
有不少不一樣值的列(例如姓名),及頻繁在 WHERE 子句中出現的列,能夠添加索引。
須要作範圍查詢的列,儘可能防在索引的後部,以便優化器能使用盡量多的索引列。
例如對於交友類網站,性別和地區是經常使用的篩選條件,且基本上使用 = 來比較,而對於年齡,則常常用範圍查詢 BETWEEN。city 選擇性一般不高(國內有幾千個城市)。sex 雖然選擇性很低,可是會在不少查詢中用到,能夠考慮建立不一樣組合索引的時候,用(sex, city)列做爲前綴。 例如(sex, city, age)。
碰到不須要 sex 的查詢,該如何使用這個具備(sex, city)前綴的索引呢?在查詢條件中新增 AND sex IN ('m', 'f')
來讓 MySQL 選擇該索引。加上這個條件不會影響結果,可是能夠匹配索引的最左前綴。但對於 city 就不行了,你得 IN 幾千個城市。
對於這個 WHERE 子句:
WHERE eye_color IN ('blue', 'brown', 'red')
AND hair_color IN ('red', 'black', 'orange', 'white')
AND sex IN ('m', 'f')
優化器會轉化成 3 * 4 * 2 總共 24 種組合,執行計劃須要檢查 WHERE 子句中的全部 24 中組合。若是組合數達到上千個,則會耗時耗內存,須要避免。
從 EXPLAIN 的輸出中很難區分 MySQL 要查詢範圍值(BETWEEN、>、< 等)仍是列表值(IN,至關於多個等值條件),這兩種狀況對應的 type 都是 range:
mysql> EXPLAIN SELECT actor_id FROM sakila.actor WHERE actor_id IN (1, 4, 99); +----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+
| 1 | SIMPLE | actor | range | PRIMARY | PRIMARY | 2 | NULL | 3 | Using where; Using index | +----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+
1 row in set (0.01 sec)
mysql> EXPLAIN SELECT actor_id FROM sakila.actor WHERE actor_id > 45; +----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+
| 1 | SIMPLE | actor | range | PRIMARY | PRIMARY | 2 | NULL | 155 | Using where; Using index | +----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
對於範圍條件查詢,MySQL 沒法再使用範圍列後面的其餘索引列了。
使用 filesort 文件排序對於小數據集是很快的,可是若是一個查詢匹配的結果又上百萬行,就須要索引。例如對於下面的查詢:
SELECT <cols> FROM profiles WHERE sex='m' ORDER BY rating LIMIT 10;
能夠建立索引(sex, rating)。這個查詢同時使用了 ORDER BY 和 LIMIT,若是沒有索引會很慢。
即便有索引,用戶翻頁到比較靠後的時候,也會很慢:
SELECT <cols> FROM profiles WHERE sex='m' ORDER BY rating LIMIT 100000,10;
能夠經過延遲關聯來優化大偏移量的數據查詢。先使用覆蓋索引查詢並返回須要的主鍵,而後根據這些主鍵關聯原表得到所需數據行。這樣能夠減小 MySQL 對須要丟棄的行的掃描。
高效使用索引(sex, rating)進行排序和分頁:
SELECT <cols> FROM profiles INNER JOIN ( SELECT <primary key cols> FROM profiles WHERE x.sex='m' ORDER BY rating LIMIT 100000, 10 ) AS x USING(<primary key cols>);
若是遇到古怪的問題,能夠嘗試 CHECK TABLE 檢查是否發生了表損壞,一般能找出大多數的表和索引錯誤。
能夠用 REPAIR TABLE 命令來修復損壞的表。若是存儲引擎不支持這個命令,能夠經過不作任何操做的 ALTER 操做來重建表,例如修改表的存儲引擎爲當前引擎。
ALTER TABLE tb ENGINE=INNODB;
另外,某些存儲引擎提供離線工具,例如 myisamchk。若是損壞的是系統區域或數據區域,而不是索引,則須要從備份中恢復表。
MySQL 的查詢優化器會經過兩個 API 來了解存儲引擎的索引值的分佈信息,以決定如何使用索引:
經過 ANALYZE TABLE 能夠從新生成統計信息。若是存儲引擎提供的掃描行數不許確,或執行計劃太複雜以至沒法準確得到各個階段匹配的行數時,優化器會使用索引統計信息來估算掃描行數。
SHOW INDEX FROM 命令能夠查看索引的基數(Cardinality),顯示了存儲引擎估算索引列有多少個不一樣的取值,也能夠經過 INFORMATION_SCHEMA.STATISTICS:
mysql> SHOW INDEX FROM sakila.actor; +-------+------------+---------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | +-------+------------+---------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| actor | 0 | PRIMARY | 1 | actor_id | A | 200 | NULL | NULL | | BTREE | | | | actor | 1 | idx_actor_last_name | 1 | last_name | A | 200 | NULL | NULL | | BTREE | | | +-------+------------+---------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 2 rows in set (0.02 sec) mysql> SELECT CARDINALITY FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_NAME='actor'; +-------------+
| CARDINALITY | +-------------+
| 200 |
| 200 | +-------------+
2 rows in set (0.02 sec)
B-Tree 索引會致使碎片化,下降查詢效率。碎片化的索引無序存儲在磁盤上。
根據設計,B-Tree 須要隨機磁盤訪問才能定位到葉子頁,沒法避免隨機訪問。可是若是葉子頁在物理分佈上是順序且緊密的,查詢性能會更好。不然對於範圍查詢、索引覆蓋掃描,速度會慢不少倍。
表的數據存儲也可能碎片化,有三種類型:
MyISAM 表會發生上面三種碎片,InnoDB 表不會出現行碎片。
能夠經過 OPTIMIZE TABLE 或導出再導入的方式從新整理數據。
MySQL 默認使用 B-Tree 索引。
在選擇索引和編寫利用索引的查詢時,三個原則:
應對措施: