十二、Mysql的索引
舉例說明索引:如果把資料庫中的某一張看成一本書,那麼索引就像是書的目錄,可以通過目錄快速查詢書中指定內容的位置,對於資料庫表來說,可以通過索引快速查詢表中的資料。
簡單來說:索引提供了類似於書中目錄的作用,目的是為了優化查詢
B樹索引 (現在使用的)
Hash索引
R樹
Full text
GIS
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、28、65 2、樹幹層:7屬於5-28之間,28為一個分支的最少值,7不應該屬於28所在分支;7大於5,7在5所在分支3、樹枝層:同理樹幹層選擇5所在分支 4、最終在葉子層查到數字7所在的真實資料的指標。最終查詢到資料內容
B-樹與B+樹基本一致,除了同一層的索引節點沒有指向下一個索引節點的雙向連結。其查詢方式也基本與B+樹一樣。
四、索引的功能性分類
1、輔助索引(S)及構建B樹結構
(1). 索引是基於表中,列(索引鍵)的值生成的B樹結構 (2). 首先提取此列所有的值,進行自動排序 (3). 將排好序的值,均勻的分佈到索引樹的葉子節點中(16K) (4). 然後生成此索引鍵值所對應得後端資料頁的指標 (5). 生成枝節點和根節點,根據資料量級和索引鍵長度,生成合適的索引樹高度 id name age genderselect * from t1 where id=10; 問題: 基於索引鍵做where查詢,對於id列是順序IO,但是對於其他列的查詢,可能是隨機IO.
輔助索引的分類
1.普通的單列輔助索引 2.聯合索引 多個列作為索引條件,生成索引樹,理論上設計的好的,可以減少大量的回表 查詢 3.唯一索引 索引列的值都是唯一的.
構建前提
(1)表中設定了主鍵,主鍵列就會自動被作為聚集索引. (2)如果沒有主鍵,會選擇唯一鍵作為聚集索引. (3)聚集索引必須在建表時才有意義,一般是表的無關列,比如ID等為主鍵
聚集索引(C)構建B樹結構
(1) 在建表時,設定了主鍵列(ID) (2) 在將來錄入資料時,就會按照ID列的順序儲存到磁碟上.(我們又稱之為聚集索引組織表) (3) 將排好序的整行資料,生成葉子節點.可以理解為,磁碟的資料頁就是葉子節點
1、聚集索引只能有一個,非空唯一,一般為主鍵列 2、輔助索引,可以有多個,是配合聚集索引使用的 3、聚集索引葉子節點,就是磁碟的資料行儲存的資料頁 4、MySQL是根據聚集索引,組織儲存資料,資料儲存時就是按照聚集索引的順序進行儲存資料 5、輔助索引,只會提取索引鍵值,進行自動排序生成B樹結構
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: 輔助索引(單列,聯和,字首)
[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; 表名 索引名
[world]>alter table city add index idx_co_po(countrycode,population);
僅用於字串,數字不能使用字首索引。
常用於字串很長的列
[world]>alter table city add index idx_di(district(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 #只能用於主鍵列或唯一列
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';
(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個例子符合索引的最左匹配,因此走了索引
比如,我們會選擇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);
(1)獲取到的是優化器選擇完成的,他認為代價最小的執行計劃. 作用: 語句執行前,先看執行計劃資訊,可以有效的防止效能較差的語句帶來的效能問題. 如果業務中出現了慢語句,我們也需要藉助此命令進行語句的評估,分析優化方案。 (2) select 獲取資料的方法 1. 全表掃描(應當儘量避免,因為效能低) 2. 索引掃描 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)
從左到右效能依次變好. 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)
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. 根據子句的執行順序,去建立聯合索引
優化前: [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
聯合索引: 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. 如果查詢中出現多子句 我們要按照子句的執行順序進行建立索引.
題目意思: 我們公司業務慢,請你從資料庫的角度分析原因 1.mysql出現效能問題,我總結有兩種情況: (1)應急性的慢:突然夯住 應急情況:資料庫hang(卡了,資源耗盡) 處理過程: 1.show processlist; 獲取到導致資料庫hang的語句 2. explain 分析SQL的執行計劃,有沒有走索引,索引的型別情況 3. 建索引,改語句 (2)一段時間慢(持續性的): (1)記錄慢日誌slowlog,分析slowlog (2)explain 分析SQL的執行計劃,有沒有走索引,索引的型別情況 (3)建索引,改語句
9.1走索引的情況
為了使索引的使用效率更高,在建立索引時,必須考慮在哪些欄位上建立索引和建立什麼型別的索引。那麼索引設計原則又是怎樣的?
2、選擇唯一性索引
唯一性索引的值是唯一的,可以更快速的通過該索引來確定某條記錄。 例如,學生表中學號是具有唯一性的欄位。為該欄位建立唯一性索引可以很快的確定某個學生的資訊。 如果使用姓名的話,可能存在同名現象,從而降低查詢速度。 優化方案: (1) 如果非得使用重複值較多的列作為查詢條件(例如:男女),可以將表邏輯拆分 (2) 可以將此列和其他的查詢類,做聯和索引 select count(*) from world.city; select count(distinct countrycode) from world.city; select count(distinct countrycode,population ) from world.city;
排序操作會浪費很多時間。 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中有個工具,專門分析索引是否有用
pt-duplicate-key-checker
表中的資料被大量更新,或者資料的使用方式被改變後,原有的一些索引可能不再需要。資料庫管理
員應當定期找出這些索引,將它們刪除,從而減少索引對更新操作的影響。
8、儘量少在經常更新值的列上建索引
9、建索引原則
(1) 必須要有主鍵,如果沒有可以做為主鍵條件的列,建立無關列 (2) 經常做為where條件列 order by group by join on, distinct 的條件(業務:產品功能+使用者行為) (3) 最好使用唯一值多的列作為索引,如果索引列重複值較多,可以考慮使用聯合索引 (4) 列值長度較長的索引列,我們建議使用字首索引. (5) 降低索引條目,一方面不要建立沒用索引,不常使用的索引清理,percona toolkit(xxxxx) (6) 索引維護要避開業務繁忙期
1、沒有查詢條件,或者查詢條件沒有建立索引
select * from tab; 全表掃描。 select * from tab where 1=1; 在業務資料庫中,特別是資料量比較大的表。 是沒有全表掃描這種需求。 1、對使用者檢視是非常痛苦的。 2、對伺服器來講毀滅性的。 (1) select * from tab; SQL改寫成以下語句: select * from tab order by price limit 10 ; 需要在price列上建立索引 (2) select * 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 ? --->鎖衝突
例子: 錯誤的例子: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>
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 專門做搜尋服務的資料庫產品