1. 程式人生 > 實用技巧 >十二、Mysql的索引

十二、Mysql的索引

一、什麼是索引

索引是對資料庫表的一列或多列的值進行排序的一種結構,使用索引可快速訪問資料庫表中的特定資訊。

舉例說明索引:如果把資料庫中的某一張看成一本書,那麼索引就像是書的目錄,可以通過目錄快速查詢書中指定內容的位置,對於資料庫表來說,可以通過索引快速查詢表中的資料。

簡單來說:索引提供了類似於書中目錄的作用,目的是為了優化查詢

二、常見索引的種類(演算法)

B樹索引  (現在使用的)
Hash索引
R樹
Full text
GIS 

三、B樹 基於不同的查詢演算法分類介紹

B樹分為: B-樹,B+樹(MyISAM和InnoDB引擎預設使用),B*樹(現在不使用)

B+樹在範圍查詢方面提供了更好的效能(> < >= <= like)

1、B+樹結構

Level 0 葉子層為B+樹的葉子節點,所有的葉子節點儲存的是指向資料的指標而非資料本身。同時每個葉子結點都有指向下一個葉子結點的雙向連結。
Level 1 枝幹層和Level 2 樹幹層就是純索引節點(資料)和主鍵,同一層的索引節點也都有指向下一個索引節點的雙向連結。

B+樹查詢過程的簡易說明,使用數字來說明其查詢過程

1、B+樹的上一層儲存的是下一層的最小數編號
2、查詢資料是從最上層及樹幹層開始向下查詢最後查詢到葉子層
3、葉子層僅儲存真實資料的指標

我們以找數字7的資料為例:

1、把樹幹分為相鄰的分支5、2865
2、樹幹層:7屬於5-28之間,28為一個分支的最少值,7不應該屬於28所在分支;7大於5,7在5所在分支
3、樹枝層:同理樹幹層選擇5所在分支 4、最終在葉子層查到數字7所在的真實資料的指標。最終查詢到資料內容

2、B-樹

B-樹與B+樹基本一致,除了同一層的索引節點沒有指向下一個索引節點的雙向連結。其查詢方式也基本與B+樹一樣。

四、索引的功能性分類

1、輔助索引(S)及構建B樹結構

(1). 索引是基於表中,列(索引鍵)的值生成的B樹結構
(2). 首先提取此列所有的值,進行自動排序
(3). 將排好序的值,均勻的分佈到索引樹的葉子節點中(16K)
(4). 然後生成此索引鍵值所對應得後端資料頁的指標
(5). 生成枝節點和根節點,根據資料量級和索引鍵長度,生成合適的索引樹高度
id  name  age  gender
select * from t1 where id=10; 問題: 基於索引鍵做where查詢,對於id列是順序IO,但是對於其他列的查詢,可能是隨機IO.

輔助索引的分類

1.普通的單列輔助索引
2.聯合索引
多個列作為索引條件,生成索引樹,理論上設計的好的,可以減少大量的回表
查詢
3.唯一索引
索引列的值都是唯一的.

2、聚集索引(C)及構建B樹結構

構建前提

(1)表中設定了主鍵,主鍵列就會自動被作為聚集索引.
(2)如果沒有主鍵,會選擇唯一鍵作為聚集索引.
(3)聚集索引必須在建表時才有意義,一般是表的無關列,比如ID等為主鍵

聚集索引(C)構建B樹結構

(1) 在建表時,設定了主鍵列(ID)
(2) 在將來錄入資料時,就會按照ID列的順序儲存到磁碟上.(我們又稱之為聚集索引組織表)
(3) 將排好序的整行資料,生成葉子節點.可以理解為,磁碟的資料頁就是葉子節點

3、聚集索引和輔助索引構成區別

1、聚集索引只能有一個,非空唯一,一般為主鍵列
2、輔助索引,可以有多個,是配合聚集索引使用的
3、聚集索引葉子節點,就是磁碟的資料行儲存的資料頁
4、MySQL是根據聚集索引,組織儲存資料,資料儲存時就是按照聚集索引的順序進行儲存資料
5、輔助索引,只會提取索引鍵值,進行自動排序生成B樹結構

4、關於索引樹的高度受什麼影響

1. 資料量級, 解決方法:分表,分庫,分散式
2. 索引列值過長 , 解決方法:字首索引
3. 資料型別:
變長長度字串,使用了char,解決方案:變長字串使用varchar
enum型別的使用enum ('山東','河北','黑龍江','吉林','遼寧','陝西'......)
                                         1      2      3
4.索引樹最高設定為4層

五、索引的管理

1、索引建立前相關資訊

[world]>desc city;
+-------------+----------+------+-----+---------+----------------+
| Field      | Type    | Null | Key | Default | Extra          |
+-------------+----------+------+-----+---------+----------------+
| ID          | int(11)  | NO  | PRI | NULL    | auto_increment |
| Name        | char(35) | NO  |    |        |                |
| CountryCode | char(3)  | NO  | MUL |        |                |
| District    | char(20) | NO  |    |        |                |
| Population  | int(11)  | NO  |    | 0      |                |
+-------------+----------+------+-----+---------+----------------+
5 rows in set (0.00 sec)

Field :列名字
key  :有沒有索引,索引型別
PRI: 主鍵索引
UNI: 唯一索引
MUL: 輔助索引(單列,聯和,字首)

2、單列普通輔助索引

[world]>alter table city add index idx_name(name);
                          表          索引名(列名)
[world]>create index idx_name1 on city(name);
[world]>show index from city;
注意:
以上操作不代表生產操作,我們不建議在一個列上建多個索引
同一個表中,索引名不能同名。
### 7.1.2 刪除索引:
db01 [world]>alter table city drop index idx_name1;
                         表名               索引名

3、覆蓋索引(聯合索引)

[world]>alter table city add index idx_co_po(countrycode,population);

4、字首索引

僅用於字串,數字不能使用字首索引。

常用於字串很長的列

[world]>alter table city add index idx_di(district(5));
注意:數字列不能用作字首索引。

5、唯一索引

只能用於主鍵列或唯一列

[world]>alter table city add unique index idx_uni1(name);
ERROR 1062 (23000): Duplicate entry 'San Jose' for key 'idx_uni1'
#不是主鍵或唯一列會報錯

[world]>alter table city add unique index idx_uni1(id);
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0
#只能用於主鍵列或唯一列

統計city表中,以省的名字為分組,統計組的個數

select district,count(id) from city group by district;
需求: 找到world下,city表中 name列有重複值的行,最後刪掉重複的行
[world]>select name,count(id) as cid from city group by name  having cid>1 order by cid desc;
[world]>select * from city where name='suzhou';

6、索引建立的原則

(1) 最左字首匹配原則

對於多列索引,總是從索引的最前面欄位開始,接著往後,中間不能跳過。比如建立了多列索引(name,age,sex),會先匹配name欄位,再匹配age欄位,再匹配sex欄位的,中間不能跳過。mysql會一直向右匹配直到遇到範圍查詢(>、<、between、like)就停止匹配。一般,在建立多列索引時,where子句中使用最頻繁的一列放在最左邊。

看一個補符合最左字首匹配原則和符合該原則的對比例子。

例項:表city建有索引(district,population)

[world]>desc select * from city where population=49;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | city  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 4188 |    10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

#該sql直接用了第二個索引欄位population,跳過了第一個索引欄位district,不符合最左字首匹配原則,因此沒有走索引



[world]>desc select * from city where district='sichuang' and population=100000;
+----+-------------+-------+------------+------+---------------+-----------+---------+-------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key       | key_len | ref         | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------------+------+----------+-------+
|  1 | SIMPLE      | city  | NULL       | ref  | idx_di_po     | idx_di_po | 24      | const,const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

[world]>desc select * from city where district='sichuang' ;
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key       | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | city  | NULL       | ref  | idx_di_po     | idx_di_po | 20      | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

#上面2個例子符合索引的最左匹配,因此走了索引

(2) 儘量選擇區分度高的列作為索引。

比如,我們會選擇ID做索引,而不會population性別來做索引。

(3) =和in可以亂序

比如a = 1 and b = 2 and c = 3,建立(a,b,c)索引可以任意順序,mysql的查詢優化器會幫你優化成索引可以識別的形式。

(4) 索引列不能參與計算,保持列“乾淨”

比如:Flistid+1>‘2000000608201108010831508721‘。原因很簡單,假如索引列參與計算的話,那每次檢索時,都會先將索引計算一次,再做比較,顯然成本太大。

(5) 儘量的擴充套件索引,不要新建索引。

比如表中已經有a的索引,現在要加(a,b)的索引,那麼只需要修改原來的索引即可。

7、索引的不足

雖然索引可以提高查詢效率,但索引也有自己的不足之處。

索引的額外開銷:

(1) 空間:索引需要佔用空間;

(2) 時間:查詢索引需要時間;

(3) 維護:索引須要維護(資料變更時);

不建議使用索引的情況:

(1) 資料量很小的表

(2) 空間緊張

8、常用索引的優化

1、有索引但未被用到的情況(不建議)

(1) Like的引數以萬用字元開頭時

儘量避免Like的引數以萬用字元開頭,否則資料庫引擎會放棄使用索引而進行全表掃描。

(2) where條件不符合最左字首原則時

例子已在最左字首匹配原則的內容中有舉例。

(3) 使用!= 或 <> 操作符時

儘量避免使用!= 或 <>操作符,否則資料庫引擎會放棄使用索引而進行全表掃描。使用>或<會比較高效。

(4) 索引列參與計算

應儘量避免在 where 子句中對欄位進行表示式操作,這將導致引擎放棄使用索引而進行全表掃描。

select * from city where id-1=9;

(5) 對欄位進行null值判斷

應儘量避免在where子句中對欄位進行null值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如: 低效:select * from city where population is null ;

可以在population上設定預設值0,確保表中population列沒有null值,然後這樣查詢: 高效:select * from city where population =0;

(6) 使用or來連線條件

應儘量避免在where子句中使用or來連線條件,否則將導致引擎放棄使用索引而進行全表掃描,如: 低效:select * from city where countrycode = 'CHN' or countrycode = 'USA';

可以用下面這樣的查詢代替上面的 or 查詢: 高效:

select * from city where countrycode="CHN";

union all

select * from city where countrycode="USA;

2、避免select *

在解析的過程中,會將'*' 依次轉換成所有的列名,這個工作是通過查詢資料字典完成的,這意味著將耗費更多的時間。

所以,應該養成一個需要什麼就取什麼的好習慣。

3、order by 語句優化

任何在Order by語句的非索引項或者有計算表示式都將降低查詢速度。

方法:1.重寫order by語句以使用索引;2.為所使用的列建立另外一個索引;3.絕對避免在order by子句中使用表示式

4、GROUP BY語句優化

提高GROUP BY 語句的效率, 可以通過將不需要的記錄在GROUP BY 之前過濾掉

低效:

SELECT JOB , AVG(SAL)

FROM EMP

GROUP by JOB

HAVING JOB = ‘PRESIDENT'

OR JOB = ‘MANAGER'

高效:

SELECT JOB , AVG(SAL)

FROM EMP

WHERE JOB = ‘PRESIDENT'

OR JOB = ‘MANAGER'

GROUP by JOB

5、用 exists 代替 in

很多時候用 exists 代替 in 是一個好的選擇: select num from a where num in(select num from b) 用下面的語句替換: select num from a where exists(select 1 from b where num=a.num)

6、使用 varchar/nvarchar 代替 char/nchar

儘可能的使用 varchar/nvarchar 代替 char/nchar ,因為首先變長欄位儲存空間小,可以節省儲存空間,其次對於查詢來說,在一個相對較小的欄位內搜尋效率顯然要高些。

7、能用DISTINCT的就不用GROUP BY

SELECT OrderID FROM Details WHERE UnitPrice > 10 GROUP BY OrderID

可改為:

SELECT DISTINCT OrderID FROM Details WHERE UnitPrice > 10

8、能用UNION ALL就不要用UNION

UNION ALL不執行SELECT DISTINCT函式,這樣就會減少很多不必要的資源。

9、在Join表的時候使用相當型別的例,並將其索引

如果應用程式有很多JOIN 查詢,你應該確認兩個表中Join的欄位是被建過索引的。這樣,MySQL內部會啟動為你優化Join的SQL語句的機制。

而且,這些被用來Join的欄位,應該是相同的型別的。例如:如果你要把 DECIMAL 欄位和一個 INT 欄位Join在一起,MySQL就無法使用它們的索引。對於那些STRING型別,還需要有相同的字符集才行。(兩個表的字符集有可能不一樣)

六、執行計劃獲取及分析

1、構建實驗環境

CREATE DATABASE oldboy CHARSET utf8mb4 COLLATE utf8mb4_bin;
USE oldboy;
CREATE TABLE t_100w(id INT,num INT,k1 CHAR(2),k2 CHAR(4),dt TIMESTAMP);

DELIMITER //
CREATE PROCEDURE rand_data(IN num INT)
BEGIN
DECLARE str CHAR(62) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
DECLARE str2 CHAR(2);
DECLARE str4 CHAR(4);
DECLARE i INT DEFAULT 0;
WHILE i<num DO
SET
str2=CONCAT(SUBSTRING(str,1+FLOOR(RAND()*61),1),SUBSTRING(str,1+FLOOR(RAND()*61),1));
SET
str4=CONCAT(SUBSTRING(str,1+FLOOR(RAND()*61),2),SUBSTRING(str,1+FLOOR(RAND()*61),2));
SET i=i+1;
INSERT INTO t_100w VALUES (i,FLOOR(RAND()*num),str2,str4,NOW());
END WHILE;
END;
//

DELIMITER;  #該命令報錯則不用輸入

插入100w條資料;
call rand_data(1000000);

2、執行計劃說明

(1)獲取到的是優化器選擇完成的,他認為代價最小的執行計劃.
作用: 語句執行前,先看執行計劃資訊,可以有效的防止效能較差的語句帶來的效能問題.
如果業務中出現了慢語句,我們也需要藉助此命令進行語句的評估,分析優化方案。
(2) select 獲取資料的方法
1. 全表掃描(應當儘量避免,因為效能低)
2. 索引掃描
3. 獲取不到資料

3、執行計劃獲取

獲取優化器選擇後的執行計劃

獲取執行計劃的命令
desc
explain

table:查詢的表
type:查詢型別
possible_keys:可能走的索引
key:走的索引名
key_len:應用索引的長度
rows:查詢結果集的長度
extra:額外資訊

關注的執行計劃的重點資訊

[world]>desc select * from city\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: city           #查詢的表明
   partitions: NULL         
         type: ALL
possible_keys: NULL           #可能會走的索引
          key: NULL           #真正走的索引
      key_len: NULL
          ref: NULL           #索引型別
         rows: 4188
     filtered: 100.00
        Extra: NULL           #額外資訊
1 row in set, 1 warning (0.00 sec)

4、索引型別詳解

從左到右效能依次變好.
ALL  :  
全表掃描,不走索引
例子:
1. 查詢條件列,沒有索引
SELECT * FROM t_100w WHERE k2='780P';  
[oldboy]>desc select * from t_100w where k2='780p';
+----+-------------+--------+------------+------+---------------+------+---------+------+--------+----------
| id | select_type|table|partitions|type|possible_keys|key|key_len| ref  | rows   | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+------+---------+------+--------+----------
|  1 | SIMPLE     |t_100w|NULL     | ALL  | NULL      |NULL|NULL  | NULL | 997589 |    10.00 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+--------+----------
1 row in set, 1 warning (0.00 sec)


2. 查詢條件出現以下語句(使用輔助索引列)
USE world 
DESC city;
DESC SELECT * FROM city WHERE countrycode <> 'CHN';
DESC SELECT * FROM city WHERE countrycode NOT IN ('CHN','USA');
DESC SELECT * FROM city WHERE countrycode LIKE '%CH%';

注意:對於聚集索引列,使用以上語句,依然會走索引
DESC SELECT * FROM city WHERE id <> 10;
[oldboy]>desc select * from world.city where id <> '10';
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id |select_type|table|partitions |type| possible_keys | key  | key_len | ref | rows |filtered | Extra   |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+---------
|  1 | SIMPLE    | city| NULL      |range| PRIMARY      |PRIMARY| 4      | NULL |2103 |100.00 |Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+---------
1 row in set, 1 warning (0.00 sec)


INDEX  :
全索引掃描
1. 查詢需要獲取整個索引列的值時:
DESC  SELECT countrycode  FROM city;
[world]>desc select countrycode from city;
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+-----
| id |select_type|table|partitions|type|possible_keys|key      | key_len | ref  |rows| filtered | Extra    |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+-----
|  1 | SIMPLE    | city| NULL     |index|NULL      | CountryCode| 3    | NULL | 4188 | 100.00| Using index |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+-----
1 row in set, 1 warning (0.00 sec)


2. 聯合索引中,任何一個非最左列作為查詢條件時:
idx_a_b_c(a,b,c)  ---> a  ab  abc

SELECT * FROM t1 WHERE b 
SELECT * FROM t1 WHERE c    

RANGE :
索引範圍掃描 
輔助索引> < >= <= LIKE IN OR 
主鍵 <>  NOT IN

例子:
1. 
DESC SELECT * FROM city WHERE id<5;
2. 
DESC SELECT * FROM city WHERE countrycode LIKE 'CH%';
3. 
DESC SELECT * FROM city WHERE countrycode IN ('CHN','USA');
[world]>desc select * from world.city where id <> '10';
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+---------
| id | select_type|table|partitions|type |possible_keys| key     |key_len|ref|rows| filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+---------
|  1 | SIMPLE     | city| NULL     |range|PRIMARY      | PRIMARY | 4     |NULL|2103| 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+---------
1 row in set, 1 warning (0.00 sec)

注意: 
1和2例子中,可以享受到B+樹的優勢,但是3例子中是不能享受的.
所以,我們可以將3號列子改寫:

DESC SELECT * FROM city WHERE countrycode='CHN'
UNION ALL 
SELECT * FROM city WHERE countrycode='USA';

ref: 
非唯一性索引,等值查詢
DESC SELECT * FROM city WHERE countrycode='CHN';
[world]>DESC SELECT * FROM city WHERE countrycode='CHN';
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+-----
| id |select_type|table|partitions|type|possible_keys| key       |key_len|ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+-----
|  1 | SIMPLE    |city | NULL     | ref|CountryCode  |CountryCode| 3     |const|  363 |  100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+-----
1 row in set, 1 warning (0.00 sec)


eq_ref: 
在多表連線時,連線條件使用了唯一索引(uk  pK)
DESC SELECT b.name,a.name FROM city AS a 
JOIN country AS b 
ON a.countrycode=b.code 
WHERE a.population <100;
DESC country

[world]>DESC SELECT b.name,a.name FROM city AS a 
    -> JOIN country AS b 
    -> ON a.countrycode=b.code 
    -> WHERE a.population <100;
+----+-------------+-------+------------+--------+---------------+---------+---------+---------------------
| id |select_type|table|partitions|type  |possible_keys|key |key_len|ref       |rows|filtered| Extra       |
+----+-------------+-------+------------+--------+---------------+---------+---------+---------------------
|  1 | SIMPLE    | a   | NULL     | ALL  |CountryCode  |NULL|NULL   | NULL      4188 | 33.33 | Using where |
|  1 | SIMPLE    | b   | NULL     | eq_ref | PRIMARY   |PRIMARY| 3 |world.a.CountryCode|  1 |100.00 | NULL |
+----+-------------+-------+------------+--------+---------------+---------+---------+---------------------
2 rows in set, 1 warning (0.00 sec

system,const :
唯一索引的等值查詢
DESC SELECT * FROM city WHERE id=10;
[world]>DESC SELECT * FROM city WHERE id=10;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
|id|select_type|table|partitions|type | possible_keys | key     | key_len | ref   | rows| filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+--------
|1 |SIMPLE     |city | NULL     |const| PRIMARY       | PRIMARY | 4       | const |    1|   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+--------
1 row in set, 1 warning (0.00 sec)

5、其他欄位解釋

extra: 
filesort ,檔案排序.
當MySQL不能使用索引進行排序時,就會利用自己的排序演算法(快速排序演算法)在記憶體(sort buffer)中對資料進行排序;如果記憶體裝載不下,它會將磁碟上的資料進行分塊,再對各個資料塊進行排序,然後將各個塊合併成有序的結果集(實際上就是外排序,使用臨時表)。

SHOW INDEX FROM city;
ALTER TABLE city ADD INDEX CountryCode(CountryCode);
ALTER TABLE city DROP INDEX idx_c_p;

DESC SELECT * FROM city WHERE countrycode='CHN'  ORDER BY population 

ALTER TABLE city ADD INDEX idx_(population);
DESC SELECT * FROM city WHERE countrycode='CHN'  ORDER BY population 
ALTER TABLE city ADD INDEX idx_c_p(countrycode,population);
ALTER TABLE city DROP INDEX idx_;
ALTER TABLE city DROP INDEX CountryCode;
DESC SELECT * FROM city WHERE countrycode='CHN'  ORDER BY population 

結論: 
1.當我們看到執行計劃extra位置出現filesort,說明由檔案排序出現
2.觀察需要排序(ORDER BY,GROUP BY ,DISTINCT )的條件,有沒有索引
3. 根據子句的執行順序,去建立聯合索引

6、索引優化效果測試

優化前:
[root@vm01 ~]# mysqlslap --defaults-file=/etc/my.cnf \
> --concurrency=100 --iterations=1 --create-schema='oldboy' \
> --query="select * from oldboy.t_100w where k2='fgcd'" engine=innodb \
> --number-of-queries=2000 -uroot -p123456 -verbose
mysqlslap: [Warning] Using a password on the command line interface can be insecure.
Benchmark
    Running for engine rbose
    Average number of seconds to run all queries: 518.454 seconds
    Minimum number of seconds to run all queries: 518.454 seconds
    Maximum number of seconds to run all queries: 518.454 seconds
    Number of clients running queries: 100
    Average number of queries per client: 20
    
建立k2的輔助索引
alter talbe t_100w add index idx_k2(k2);
    
    
    優化後:
[root@vm01 ~]# mysqlslap --defaults-file=/etc/my.cnf \
> --concurrency=100 --iterations=1 --create-schema='oldboy' \
> --query="select * from oldboy.t_100w where k2='fgcd'" engine=innodb \
> --number-of-queries=2000 -uroot -p123456 -verbose
mysqlslap: [Warning] Using a password on the command line interface can be insecure.
Benchmark
    Running for engine rbose
    Average number of seconds to run all queries: 1.584 seconds
    Minimum number of seconds to run all queries: 1.584 seconds
    Maximum number of seconds to run all queries: 1.584 seconds
    Number of clients running queries: 100
    Average number of queries per client: 20

7、聯合索引

聯合索引:
1. SELECT * FROM t1  WHERE a=    b=   
我們建立聯合索引時:
ALTER TABLE t1 ADD INDEX idx_a_b(a,b);  
ALTER TABLE t1 ADD INDEX idx_b_a(b,a);  
以上的查詢不考慮索引的順序,優化器會自動調整where的條件順序
注意: 索引,我們在這種情況下建索引時,需要考慮哪個列的唯一值更多,哪個放在索引左邊.

2.  如果出現where 條件中出現不等值查詢條件
DESC  SELECT * FROM t_100w WHERE num <1000 AND k2='DEEF';
我們建索引時:
ALTER TABLE t_100w ADD INDEX idx_2_n(k2,num);
語句書寫時
DESC  SELECT * FROM t_100w WHERE  k2='DEEF'  AND  num <1000 ;
3. 如果查詢中出現多子句
我們要按照子句的執行順序進行建立索引.

8、explain(desc)使用場景

題目意思:  我們公司業務慢,請你從資料庫的角度分析原因
1.mysql出現效能問題,我總結有兩種情況:
(1)應急性的慢:突然夯住
應急情況:資料庫hang(卡了,資源耗盡)
處理過程:
1.show processlist;  獲取到導致資料庫hang的語句
2. explain 分析SQL的執行計劃,有沒有走索引,索引的型別情況
3. 建索引,改語句
(2)一段時間慢(持續性的):
(1)記錄慢日誌slowlog,分析slowlog
(2)explain 分析SQL的執行計劃,有沒有走索引,索引的型別情況
(3)建索引,改語句

9、索引應用規範

9.1走索引的情況

為了使索引的使用效率更高,在建立索引時,必須考慮在哪些欄位上建立索引和建立什麼型別的索引。那麼索引設計原則又是怎樣的?

1、(必須的) 建表時一定要有主鍵,一般是個無關列

2、選擇唯一性索引

唯一性索引的值是唯一的,可以更快速的通過該索引來確定某條記錄。
例如,學生表中學號是具有唯一性的欄位。為該欄位建立唯一性索引可以很快的確定某個學生的資訊。
如果使用姓名的話,可能存在同名現象,從而降低查詢速度。

優化方案:
(1) 如果非得使用重複值較多的列作為查詢條件(例如:男女),可以將表邏輯拆分
(2) 可以將此列和其他的查詢類,做聯和索引
select count(*) from world.city;
select count(distinct countrycode) from world.city;
select count(distinct countrycode,population ) from world.city;

3、(必須的) 為經常需要where 、ORDER BY、GROUP BY,join on等操作的欄位,建立索引

排序操作會浪費很多時間。
where  A B C      ----》 A  B  C
in 
where A   group by B  order by C
A,B,C

如果為其建立索引,優化查詢
注:如果經常作為條件的列,重複值特別多,可以建立聯合索引。

4、儘量使用字首來索引

如果索引欄位的值很長,最好使用值的字首來索引。

5、限制索引的數目

索引的數目不是越多越好。
可能會產生的問題:
(1) 每個索引都需要佔用磁碟空間,索引越多,需要的磁碟空間就越大。
(2) 修改表時,對索引的重構和更新很麻煩。越多的索引,會使更新表變得很浪費時間。
(3) 優化器的負擔會很重,有可能會影響到優化器的選擇.
percona-toolkit中有個工具,專門分析索引是否有用

6、刪除不再使用或者很少使用的索引(percona toolkit)

pt-duplicate-key-checker

表中的資料被大量更新,或者資料的使用方式被改變後,原有的一些索引可能不再需要。資料庫管理
員應當定期找出這些索引,將它們刪除,從而減少索引對更新操作的影響。

7、大表加索引,要在業務不繁忙期間操作

8、儘量少在經常更新值的列上建索引

9、建索引原則

(1) 必須要有主鍵,如果沒有可以做為主鍵條件的列,建立無關列
(2) 經常做為where條件列  order by  group by  join on, distinct 的條件(業務:產品功能+使用者行為)
(3) 最好使用唯一值多的列作為索引,如果索引列重複值較多,可以考慮使用聯合索引
(4) 列值長度較長的索引列,我們建議使用字首索引.
(5) 降低索引條目,一方面不要建立沒用索引,不常使用的索引清理,percona toolkit(xxxxx)
(6) 索引維護要避開業務繁忙期

9.2不走索引的情況

1、沒有查詢條件,或者查詢條件沒有建立索引

select * from tab;       全表掃描。
select  * from tab where 1=1;
在業務資料庫中,特別是資料量比較大的表。
是沒有全表掃描這種需求。
1、對使用者檢視是非常痛苦的。
2、對伺服器來講毀滅性的。
(1select * from tab;
SQL改寫成以下語句:
select  * from  tab  order by  price  limit 10 ;    需要在price列上建立索引
(2select  * from  tab where name='zhangsan'          name列沒有索引
改:
1、換成有索引的列作為查詢條件
2、將name列建立索引

2、查詢結果集是原表中的大部分資料,應該是25%以上。

查詢的結果集,超過了總數行數25%,優化器覺得就沒有必要走索引了。

假如:tab表 id,name    id:1-100w  ,id列有(輔助)索引
select * from tab  where id>500000;
如果業務允許,可以使用limit控制。
怎麼改寫 ?
結合業務判斷,有沒有更好的方式。如果沒有更好的改寫方案
儘量不要在mysql存放這個資料了。放到redis裡面。

3、 索引本身失效,統計資料不真實

索引有自我維護的能力。
對於表內容變化比較頻繁的情況下,有可能會出現索引失效。
一般是刪除重建

現象:
有一條select語句平常查詢時很快,突然有一天很慢,會是什麼原因
select?  --->索引失效,,統計資料不真實
DML ?   --->鎖衝突

4、查詢條件使用函式在索引列上,或者對索引列進行運算,運算包括(+,-,*,/,! 等)

例子:
錯誤的例子:select * from test where id-1=9;
正確的例子:select * from test where id=10;
算術運算
函式運算
子查詢

5、隱式轉換導致索引失效.這一點應當引起重視.也是開發中經常會犯的錯誤.

這樣會導致索引失效. 錯誤的例子:
mysql> alter table tab add index inx_tel(telnum);
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0
mysql>
mysql> desc tab;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id    | int(11)    | YES  |    | NULL    |      |
| name  | varchar(20) | YES  |    | NULL    |      |
| telnum | varchar(20) | YES  | MUL | NULL    |      |
+--------+-------------+------+-----+---------+-------+
3 rows in set (0.01 sec)
mysql> select * from tab where telnum='1333333';
+------+------+---------+
| id  | name | telnum  |
+------+------+---------+
|    1 | a    | 1333333 |
+------+------+---------+
1 row in set (0.00 sec)
mysql> select * from tab where telnum=1333333;
+------+------+---------+
| id  | name | telnum  |
+------+------+---------+
|    1 | a    | 1333333 |
+------+------+---------+
1 row in set (0.00 sec)
mysql> explain  select * from tab where telnum='1333333';
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key    | key_len | ref  | rows | Extra                |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+

|  1 | SIMPLE      | tab  | ref  | inx_tel      | inx_tel | 63      | const |    1 | Using index condition |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
1 row in set (0.00 sec)
mysql> explain  select * from tab where telnum=1333333;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra      |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | tab  | ALL  | inx_tel      | NULL | NULL    | NULL |    2 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
mysql> explain  select * from tab where telnum=1555555;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra      |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | tab  | ALL  | inx_tel      | NULL | NULL    | NULL |    2 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
mysql> explain  select * from tab where telnum='1555555';
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key    | key_len | ref  | rows | Extra                |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
|  1 | SIMPLE      | tab  | ref  | inx_tel      | inx_tel | 63      | const |    1 | Using index condition |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
1 row in set (0.00 sec)
mysql>

6、<> ,not in 不走索引(輔助索引)

EXPLAIN  SELECT * FROM teltab WHERE telnum  <> '110';
EXPLAIN  SELECT * FROM teltab WHERE telnum  NOT IN ('110','119');

mysql> select * from tab where telnum <> '1555555';
+------+------+---------+
| id  | name | telnum  |
+------+------+---------+
|    1 | a    | 1333333 |
+------+------+---------+
1 row in set (0.00 sec)
mysql> explain select * from tab where telnum <> '1555555';

單獨的>,<,in 有可能走,也有可能不走,和結果集有關,儘量結合業務新增limit
or或in  儘量改成union
EXPLAIN  SELECT * FROM teltab WHERE telnum  IN ('110','119');
改寫成:
EXPLAIN SELECT * FROM teltab WHERE telnum='110'
UNION ALL
SELECT * FROM teltab WHERE telnum='119'

7、 like "%_" 百分號在最前面不走

EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '31%'  走range索引掃描
EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '%110'  不走索引
%linux%類的搜尋需求,可以使用elasticsearch+mongodb 專門做搜尋服務的資料庫產品