MySQL優化:索引

前言

索引是關係型數據庫優化時最常見成本最低的一種優化方式。

在關係數據庫中,索引是一種單獨的、物理的對數據庫表中一列或多列的值進行排序的一種存儲結構,它是某個表中一列或若干列值的集合和相應的指向表中物理標識這些值的數據頁的邏輯指針清單。

如果一張表是一本書,那麼索引就相當於目錄。查找指定的內容不需要每一頁每一頁的查找,只需要先查找目錄,再直接翻到指定的位置即可。

前置閱讀

由於本篇主要利用explain關鍵字來分析索引使用情況,所以先了解explain關鍵字很有必要

SQL腳本

本篇使用的SQL腳本如下

create table staffs(
    id int primary key auto_increment,
    name varchar(24) not null default '' comment '姓名',
    age int not null default 0 comment '年齡',
    pos varchar(20) not null default '' comment '職位',
    add_time timestamp not null default current_timestamp comment '入職時間'
  ) charset utf8 comment '員工記錄表';

-- 創建聯合索引
alter table staffs add index idx_nap(name, age, pos);

-- 表結構來自sakila數據庫(直接導入sakila數據庫即可)
-- 地址:https://downloads.mysql.com/docs/sakila-db.zip
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 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 KEY `rental_date` (`rental_date`,`inventory_id`,`customer_id`),
  KEY `idx_fk_inventory_id` (`inventory_id`),
  KEY `idx_fk_customer_id` (`customer_id`),
  KEY `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
) ENGINE=InnoDB AUTO_INCREMENT=16050 DEFAULT CHARSET=utf8mb4

由於SQL優化器的行爲受到表中數據量的影響比較大,所以當使用explain關鍵字分析出的結果與理論不符合時,請在表中插入大量數據。
下面利用MySQL的存儲過程往表中插入一千萬數據,以便測試的時候能更好的看到效果
表結構如下:

-- 表結構
CREATE TABLE `sicimike` (
  `id` INT(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
  `name` VARCHAR(24) NOT NULL DEFAULT '',
  `age` INT(11) NOT NULL DEFAULT '0',
  `add_time` DATETIME NOT NULL DEFAULT now()
) ENGINE=INNODB DEFAULT CHARSET=utf8;

編寫存儲過程,往sicimike表中插入一千萬條數據

-- 存儲過程
DELIMITER $$

DROP PROCEDURE IF EXISTS `sicimike_data`$$

CREATE PROCEDURE `sicimike_data`()
	BEGIN
		DECLARE i  INT DEFAULT 0;
		-- 關閉自動提交
		SET SESSION autocommit=0;
		 WHILE i  < 10000000 DO
			 INSERT INTO `sicimike` ( `name`,  `age`)
			 VALUES(SUBSTRING(MD5(RAND()),1,10),  FLOOR(RAND()*100));
			 SET  i = i + 1;
		 END WHILE;
		 -- 提交
		 COMMIT;
	END$$

DELIMITER ;

編譯之後調用

call sicimike_data()

執行大概花了4分多鐘,數據新增成功後,創建索引

alter table sicimike add index idx_na(name, age)

再查看數據量

mysql> select count(*) from sicimike;
+----------+
| count(*) |
+----------+
| 10000000 |
+----------+
1 row in set (3.16 sec)

索引

索引分類

MySQL(5.6)支持的索引可以分爲五類:主鍵索引唯一索引普通索引組合索引全文索引。其中組合索引就是由多個列聯合創建的索引。

大多數MySQL索引(PRIMARY KEYUNIQUEINDEXFULLTEXT)存儲在B+樹中。例外:空間數據類型的索引使用R樹; MEMORY(MySQl存儲引擎的一種)表還支持哈希索引InnoDBFULLTEXT索引使用反向列表。

索引作用

MySQL可以利用索引進行以下操作:

  • 快速查找與WHERE子句匹配的行。
  • 從consideration中消除行。如果可以在多個索引之間進行選擇,則MySQL通常會使用查找最少行數的索引(最具選擇性的索引)。
  • 如果表具有多列索引,那麼優化器可以使用索引的任何最左前綴來查找行。例如,如果在(col1,col2,col3)上有一個三列索引,則在(col1),(col1,col2)和(col1,col2,col3)上具有索引搜索功能
  • 當有表連接的時候,從其他表檢索行數據。如果聲明相同的類型和大小,MySQL可以更有效地在列上使用索引。
  • 查找特定索引列key_colMIN()MAX()值。
  • 如果排序或分組是在可用索引的最左前綴上完成的,則可以利用索引對錶進行排序或分組。如果在所有關鍵部分後面都有DESC,則按相反順序讀取key
  • 在某些情況下,查詢可以優化爲檢索值而無需查詢數據行爲查詢提供所有必要結果的索引稱爲覆蓋索引)。如果查詢僅從表中使用某些索引中包含的列,則可以從索引樹中檢索所選值以提高速度

也就是說在優化SQL時,如果想優化以上的問題,才需要使用索引,由此可見,索引雖然強大,但是也有適用範圍。

匹配方式

索引的匹配方式是指在什麼情況下會使用到索引。索引的匹配方式有六種:全值匹配最左前綴匹配(組合索引)匹配列前綴匹配範圍值精確匹配前面的列並範圍匹配另外一列只訪問索引的查詢(覆蓋索引)

  • 全值匹配: 全值匹配指的是和索引中的所有列進行匹配
    查詢方式如下

    explain select * from staffs where name = 'July' and age = '23' and pos = 'dev';
    

    執行結果

    mysql> explain select * from staffs where name = 'July' and age = '23' and pos = 'dev';
    +----+-------------+--------+------+---------------+---------+---------+-------------------+------+-----------------------+
    | id | select_type | table  | type | possible_keys | key     | key_len | ref               | rows | Extra                 |
    +----+-------------+--------+------+---------------+---------+---------+-------------------+------+-----------------------+
    |  1 | SIMPLE      | staffs | ref  | idx_nap       | idx_nap | 140     | const,const,const |    1 | Using index condition |
    +----+-------------+--------+------+---------------+---------+---------+-------------------+------+-----------------------+
    1 row in set (0.01 sec)
    
  • 最左前綴匹配:只匹配(組合)索引的前面的一列或者多列
    查詢方式如下

    explain select * from staffs where name = 'July' and age = '23';
    

    執行結果

    mysql> explain select * from staffs where name = 'July' and age = '23';
    +----+-------------+--------+------+---------------+---------+---------+-------------+------+-----------------------+
    | id | select_type | table  | type | possible_keys | key     | key_len | ref         | rows | Extra                 |
    +----+-------------+--------+------+---------------+---------+---------+-------------+------+-----------------------+
    |  1 | SIMPLE      | staffs | ref  | idx_nap       | idx_nap | 78      | const,const |    1 | Using index condition |
    +----+-------------+--------+------+---------------+---------+---------+-------------+------+-----------------------+
    1 row in set (0.00 sec)
    

    可以看到,只查詢nameage依然可以使用idx_nap索引,也驗證了前文索引作用裏的第三條

  • 匹配列前綴:可以匹配某一列的值的開頭部分(注意區分於最左前綴匹配
    查詢方式如下

    explain select * from staffs where name like 'J%';
    

    執行結果

    mysql> explain select * from staffs where name like 'J%';
    +----+-------------+--------+-------+---------------+---------+---------+------+------+-----------------------+
    | id | select_type | table  | type  | possible_keys | key     | key_len | ref  | rows | Extra                 |
    +----+-------------+--------+-------+---------------+---------+---------+------+------+-----------------------+
    |  1 | SIMPLE      | staffs | range | idx_nap       | idx_nap | 74      | NULL |    1 | Using index condition |
    +----+-------------+--------+-------+---------------+---------+---------+------+------+-----------------------+
    1 row in set (0.00 sec)
    

    只匹配name列的前綴部分,依然可以使用到索引。稍微修改下這條SQL,執行得到結果

    mysql> explain select * from staffs where name like '%J';
    +----+-------------+--------+------+---------------+------+---------+------+------+-------------+
    | id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows | Extra       |
    +----+-------------+--------+------+---------------+------+---------+------+------+-------------+
    |  1 | SIMPLE      | staffs | ALL  | NULL          | NULL | NULL    | NULL |    1 | Using where |
    +----+-------------+--------+------+---------------+------+---------+------+------+-------------+
    1 row in set (0.00 sec)
    

    可以看到把%放在字符串的前面,就不能使用索引,這也就是有些文章說的:模糊查詢可能會使索引失效。

  • 匹配範圍值:可以查找某一個範圍的數據
    查詢方式如下

    explain select * from staffs where name > 'Mary';
    

    執行結果

    mysql> explain select * from staffs where name > 'Mary';
    +----+-------------+--------+-------+---------------+---------+---------+------+------+-----------------------+
    | id | select_type | table  | type  | possible_keys | key     | key_len | ref  | rows | Extra                 |
    +----+-------------+--------+-------+---------------+---------+---------+------+------+-----------------------+
    |  1 | SIMPLE      | staffs | range | idx_nap       | idx_nap | 74      | NULL |    1 | Using index condition |
    +----+-------------+--------+-------+---------------+---------+---------+------+------+-----------------------+
    1 row in set (0.00 sec)
    

    可以看到根據name列進行範圍查詢,也可以使用到索引,但是使用別的列可不行

    mysql> explain select * from staffs where age > 20;
    +----+-------------+--------+------+---------------+------+---------+------+------+-------------+
    | id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows | Extra       |
    +----+-------------+--------+------+---------------+------+---------+------+------+-------------+
    |  1 | SIMPLE      | staffs | ALL  | NULL          | NULL | NULL    | NULL |    1 | Using where |
    +----+-------------+--------+------+---------------+------+---------+------+------+-------------+
    1 row in set (0.00 sec)
    

    可以看到使用age列進行範圍查詢就不行,具體原因參照最左前綴匹配

  • 精確匹配前面的列並範圍匹配另外一列:也就是把範圍匹配放在最後
    查詢方式如下

    explain select * from staffs where name = 'July' and age > 25
    

    執行結果

    mysql> explain select * from staffs where name = 'July' and age > 25;
    +----+-------------+--------+-------+---------------+---------+---------+------+------+-----------------------+
    | id | select_type | table  | type  | possible_keys | key     | key_len | ref  | rows | Extra                 |
    +----+-------------+--------+-------+---------------+---------+---------+------+------+-----------------------+
    |  1 | SIMPLE      | staffs | range | idx_nap       | idx_nap | 78      | NULL |    1 | Using index condition |
    +----+-------------+--------+-------+---------------+---------+---------+------+------+-----------------------+
    1 row in set (0.00 sec)
    
  • 只訪問索引的查詢:查詢的時候只需要訪問索引,不需要訪問數據行,本質上就是覆蓋索引
    查詢方式如下

    explain select name,age,pos from staffs where name = 'July' and age = 25 and pos = 'dev';
    

    執行結果

    mysql> explain select name,age,pos from staffs where name = 'July' and age = 25 and pos = 'dev';
    +----+-------------+--------+------+---------------+---------+---------+-------------------+------+--------------------------+
    | id | select_type | table  | type | possible_keys | key     | key_len | ref               | rows | Extra                    |
    +----+-------------+--------+------+---------------+---------+---------+-------------------+------+--------------------------+
    |  1 | SIMPLE      | staffs | ref  | idx_nap       | idx_nap | 140     | const,const,const |    1 | Using where; Using index |
    +----+-------------+--------+------+---------------+---------+---------+-------------------+------+--------------------------+
    1 row in set (0.00 sec)
    

    因爲組合索引是根據nameagepos三列創建的,而該條SQL語句返回的列和過濾條件都沒有涉及到別的列,所以會發生所以索引覆蓋。有的地方也叫覆蓋索引,比較容易會被誤解成一種索引類型,但是實際上是一種現象,所以博主習慣叫索引覆蓋

數據結構

MySQL索引使用的數據結構主要有兩種:B+哈希
B+樹乃至在數據結構中佔有非常大的比重,所以單獨寫了一篇文章來介紹從二叉樹到B+樹(尚未完成),此處不再贅述。

哈希索引顧名思義是基於哈希表的實現。

Hash,一般翻譯做散列、雜湊,或音譯爲哈希,是把任意長度的輸入(又叫做預映射pre-image)通過散列算法變換成固定長度的輸出,該輸出就是散列值。這種轉換是一種壓縮映射,也就是,散列值的空間通常遠小於輸入的空間,不同的輸入可能會散列成相同的輸出,所以不可能從散列值來確定唯一的輸入值。簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數。

哈希索引自身只需存儲對應的hash值,所以索引的結構十分緊湊,這讓哈希索引查找的速度非常快。結構緊湊、查找速度快,甚至可以說是哈希索引的唯一優點了。

哈希索引的缺點或者說侷限性非常多:

  • 只能進行精確匹配而不能進行模糊查找或者範圍匹配
  • 哈希索引只包含哈希值行指針,而不存儲字段值,所以不能使用索引中的值來避免讀取行 (不支持索引覆蓋
  • 哈希索引數據並不是按照索引值順序存儲的,所以無法進行排序
  • 哈希索引不支持部分列匹配查找,哈希索引是使用索引列的全部內容來計算哈希值
  • 哈希衝突比較嚴重的時候,查找效率明顯降低,維護的代價也比較高

在MySQL中,只有memory類型的存儲引擎顯式支持哈希索引。

存儲方式

我們常常聽見聚簇索引非聚簇索引這連個詞,但是這兩個詞並不是指索引類型,而是指數據的存儲方式。

聚簇索引也叫簇類索引,是一種對磁盤上實際數據重新組織以按指定的一個或多個列的值排序。由於聚簇索引的索引頁面指針指向數據頁面,所以使用聚簇索引查找數據幾乎總是比使用非聚簇索引快。每張表只能建一個聚簇索引,並且建聚簇索引需要至少相當該表120%的附加空間,以存放該表的副本和索引中間頁。
非聚簇索引,索引的一種。索引分爲聚簇索引和非聚簇索引兩種。建立索引的目的是加快對錶中記錄的查找或排序。索引順序與數據物理排列順序無關。

由於聚簇索引代表了數據在表中的物理存儲順序,因此一個表只能包含一個聚簇索引

聚簇索引文件中每個搜索碼值都對應一個索引值,葉子節點保存的不只是鍵值,還保存了該條記錄其他列的信息。也就是根據索引值可以直接找到該條記錄的所有字段信息。

非聚簇索引只爲索引碼的某些值建立索引項,葉子節點只保存鍵值該列地址或者鍵值和主鍵,找到鍵值後還需根據地址或者主鍵找到該條記錄,也就是回表操作。但是,也不是所有的非聚簇索引查找都需要回表操作,如果查詢的SELECT子句和WHERE子句後面出現的列,均在同一個非聚餐索引中,這時候可以直接從索引中返回數據,而不需要進行回表操作,這種操作就叫索引覆蓋

沒有回表操作,查詢自然會快很多,所以索引覆蓋也是常用的優化手段。

MyISAM中所有索引均爲非聚簇索引,InnoDB有且僅有一個聚簇索引,聚簇索引選取規則如下:

  • 若有一個主鍵被定義,則該主鍵就是聚簇索引
  • 如果沒有主鍵,該表第一個非空唯一索引就是聚簇索引
  • 如果不滿足以上條件,InnoDB會生成一個隱藏的主鍵作爲聚簇索引

最佳實踐

想要優化SQL,首先得知道哪些SQL需要被優化,除了前面一篇:MySQL優化:explain、show profile和show processlist 提到的命令,MySQL還提供了一種記錄慢查詢日誌的方式。

  • 開啓慢查詢日誌
    SHOW VARIABLES LIKE '%query%'
    
    執行結果
    mysql> SHOW VARIABLES LIKE '%query%';
    +------------------------------+---------------+
    | Variable_name                | Value         |
    +------------------------------+---------------+
    | binlog_rows_query_log_events | OFF           |
    | ft_query_expansion_limit     | 20            |
    | have_query_cache             | YES           |
    | long_query_time              | 10.000000     |
    | query_alloc_block_size       | 8192          |
    | query_cache_limit            | 1048576       |
    | query_cache_min_res_unit     | 4096          |
    | query_cache_size             | 0             |
    | query_cache_type             | OFF           |
    | query_cache_wlock_invalidate | OFF           |
    | query_prealloc_size          | 8192          |
    | slow_query_log               | ON            |
    | slow_query_log_file          | *-slow.log |
    +------------------------------+---------------+
    13 rows in set (0.01 sec)
    
    slow_query_log設置成onslow_query_log_file是慢日誌存放目錄,long_query_time是查詢超過該時間就認爲是慢查詢,單位是秒(s),根據自己需要調整
    SHOW STATUS LIKE 'Slow_queries'命令可以查看慢查詢日誌中記錄了幾條SQL
  • explain關鍵字分析SQL執行計劃
    explain select * from table ...
    
  • 根據explain分析結果進行針對性優化

對於SQL語句的優化,有一些比較通用的規則,現在整理如下:

  • 索引並不是越多越好,一張表中建議不超過5個。因爲索引本身也佔資源,再者數據的更新需要維護索引
  • 單個組合索引字段數不允許超過5個
  • 索引儘量建在where後面經常使用的字段上,參考索引作用的第一條
  • 索引必須建立在離散程度大的列上,比如身份證號、員工工號等,而不是性別、職位等字段,因爲性別、職位等字段重複值太多,篩選度低
  • 禁止在select語句後面使用*,而是寫出具體要返回的列
  • 當使用索引列進行查詢的時候儘量不要使用表達式
    在驗證這條規則前,先添加一個索引
    -- age 列創建索引
    alter table staffs add index idx_age(age)
    
    仔細觀察這兩條SQL語句的執行計劃,結果一目瞭然
    mysql> explain select * from staffs where age = 18;
    +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+
    | id | select_type | table  | type | possible_keys | key     | key_len | ref   | rows | Extra |
    +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+
    |  1 | SIMPLE      | staffs | ref  | idx_age       | idx_age | 4       | const |    1 | NULL  |
    +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+
    1 row in set (0.00 sec)
    
    mysql> explain select * from staffs where age + 1 = 19;
    +----+-------------+--------+------+---------------+------+---------+------+------+-------------+
    | id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows | Extra       |
    +----+-------------+--------+------+---------------+------+---------+------+------+-------------+
    |  1 | SIMPLE      | staffs | ALL  | NULL          | NULL | NULL    | NULL |    1 | Using where |
    +----+-------------+--------+------+---------------+------+---------+------+------+-------------+
    1 row in set (0.00 sec)
    
  • 儘量使用主鍵查詢,而不是其他索引,因爲主鍵查詢不會觸發回表查詢
  • 強制類型轉換可能會全表掃描(包括where條件和連接查詢時的連接條件)
    mysql> explain select * from staffs where name = '1234';
    +----+-------------+--------+------+---------------+---------+---------+-------+------+-----------------------+
    | id | select_type | table  | type | possible_keys | key     | key_len | ref   | rows | Extra                 |
    +----+-------------+--------+------+---------------+---------+---------+-------+------+-----------------------+
    |  1 | SIMPLE      | staffs | ref  | idx_nap       | idx_nap | 74      | const |    1 | Using index condition |
    +----+-------------+--------+------+---------------+---------+---------+-------+------+-----------------------+
    1 row in set (0.00 sec)
    
    mysql> explain select * from staffs where name = 1234;
    +----+-------------+--------+------+---------------+------+---------+------+------+-------------+
    | id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows | Extra       |
    +----+-------------+--------+------+---------------+------+---------+------+------+-------------+
    |  1 | SIMPLE      | staffs | ALL  | idx_nap       | NULL | NULL    | NULL |    1 | Using where |
    +----+-------------+--------+------+---------------+------+---------+------+------+-------------+
    1 row in set (0.00 sec)
    
  • 利用索引完成排序(order by)操作
    創建索引的時候,索引本身就是按照升序排列的,如果排序的規則能利用索引來排序,就不需要用文件來排序(extra不會出現using filesort)。只有當索引的列順序和order by子句的順序完全一致,並且所有列的排序方式都一樣時,MySQL才能夠使用索引來對結果進行排序,如果查詢需要關聯多張表,則只有當order by子句引用的字段全部爲第一張表時,才能使用索引做排序。order by子句和查詢子句的限制是一樣的,需要滿足索引的最左前綴的要求。否則,MySQL都需要執行額外的排序操作,而無法利用索引排序。仔細觀察以下幾條SQL語句的執行計劃就明白了,主要關注typeExtra
    mysql> explain select rental_id, staff_id from rental where rental_date = '2005-05-25' order by inventory_id asc, customer_id asc;
    +----+-------------+--------+------+---------------+-------------+---------+-------+------+-------------+
    | id | select_type | table  | type | possible_keys | key         | key_len | ref   | rows | Extra       |
    +----+-------------+--------+------+---------------+-------------+---------+-------+------+-------------+
    |  1 | SIMPLE      | rental | ref  | rental_date   | rental_date | 5       | const |    1 | Using where |
    +----+-------------+--------+------+---------------+-------------+---------+-------+------+-------------+
    1 row in set (0.00 sec)
    
    mysql> explain select rental_id, staff_id from rental where rental_date = '2005-05-25' order by inventory_id asc, customer_id desc;
    +----+-------------+--------+------+---------------+-------------+---------+-------+------+-----------------------------+
    | id | select_type | table  | type | possible_keys | key         | key_len | ref   | rows | Extra                       |
    +----+-------------+--------+------+---------------+-------------+---------+-------+------+-----------------------------+
    |  1 | SIMPLE      | rental | ref  | rental_date   | rental_date | 5       | const |    1 | Using where; Using filesort |
    +----+-------------+--------+------+---------------+-------------+---------+-------+------+-----------------------------+
    1 row in set (0.00 sec)
    
    mysql> explain select rental_id, staff_id from rental where rental_date > '2005-05-25' order by inventory_id asc, customer_id desc;
    +----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+
    | id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows  | Extra                       |
    +----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+
    |  1 | SIMPLE      | rental | ALL  | rental_date   | NULL | NULL    | NULL | 16008 | Using where; Using filesort |
    +----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+
    1 row in set (0.00 sec)
    
    mysql> explain select rental_id, staff_id from rental order by inventory_id asc, customer_id desc;
    +----+-------------+--------+------+---------------+------+---------+------+-------+----------------+
    | id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows  | Extra          |
    +----+-------------+--------+------+---------------+------+---------+------+-------+----------------+
    |  1 | SIMPLE      | rental | ALL  | NULL          | NULL | NULL    | NULL | 16008 | Using filesort |
    +----+-------------+--------+------+---------------+------+---------+------+-------+----------------+
    1 row in set (0.00 sec)
    
    第1條和第2條對比,說明order by的排序方式要和索引保持一致,或者order by後面的兩個字段都按照desc排序也是可以的;第1條和第3條對比,說明範圍查找會使組合索引中,範圍查找條件之後的列索引失效,也就是>篩選符號之後的列不能用到索引;第1條和第4條對比,說明order by子句和where一樣也需要滿足最左前綴
  • 表中儘量不要有爲null的數據,可以用默認值代替
  • union all, in, or都能夠使用索引,但是推薦使用in,仔細觀察以下幾條SQL語句的執行計劃
    mysql> explain select * from sicimike where name = '1' or name = '2';
    +----+-------------+----------+-------+---------------+--------+---------+------+------+-----------------------+
    | id | select_type | table    | type  | possible_keys | key    | key_len | ref  | rows | Extra                 |
    +----+-------------+----------+-------+---------------+--------+---------+------+------+-----------------------+
    |  1 | SIMPLE      | sicimike | range | idx_na        | idx_na | 74      | NULL |    2 | Using index condition |
    +----+-------------+----------+-------+---------------+--------+---------+------+------+-----------------------+
    1 row in set (0.00 sec)
    
    mysql> explain select * from sicimike where name in ('1', '2');
    +----+-------------+----------+-------+---------------+--------+---------+------+------+-----------------------+
    | id | select_type | table    | type  | possible_keys | key    | key_len | ref  | rows | Extra                 |
    +----+-------------+----------+-------+---------------+--------+---------+------+------+-----------------------+
    |  1 | SIMPLE      | sicimike | range | idx_na        | idx_na | 74      | NULL |    2 | Using index condition |
    +----+-------------+----------+-------+---------------+--------+---------+------+------+-----------------------+
    1 row in set (0.00 sec)
    
    mysql> explain select * from sicimike where name = '1' union all select * from sicimike where name = '2';
    +----+--------------+------------+------+---------------+--------+---------+-------+------+-----------------------+
    | id | select_type  | table      | type | possible_keys | key    | key_len | ref   | rows | Extra                 |
    +----+--------------+------------+------+---------------+--------+---------+-------+------+-----------------------+
    |  1 | PRIMARY      | sicimike   | ref  | idx_na        | idx_na | 74      | const |    1 | Using index condition |
    |  2 | UNION        | sicimike   | ref  | idx_na        | idx_na | 74      | const |    1 | Using index condition |
    | NULL | UNION RESULT | <union1,2> | ALL  | NULL          | NULL   | NULL    | NULL  | NULL | Using temporary       |
    +----+--------------+------------+------+---------------+--------+---------+-------+------+-----------------------+
    3 rows in set (0.00 sec)
    
    可以看到union all, in, or都能夠使用索引,如果數據量小的話就不一定了。
  • 範圍列可以用到索引,但是範圍列後面的列無法用到索引,包括:<<=>>=between
  • 模糊查詢不要以通配符開頭
  • 能使用union all就不使用union,因爲union all不需要執行類似distinct操作
  • 使用group by語句時,儘量先過濾再分組。即把條件寫在where子句裏而不是having子句

參考

  • https://dev.mysql.com/doc/refman/5.6/en/mysql-indexes.html
發佈了55 篇原創文章 · 獲贊 107 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章