MySQL慢sql分析及優化

注:因爲在MySQL平常查詢中,查詢類型的語句佔慢sql的大部分,所以本文僅針對query類型的sql進行闡述,modify類型會單獨分析。mysql

爲什麼要對慢SQL進行治理

從數據庫角度看:每一個SQL執行都須要消耗必定I/O資源,SQL執行的快慢,決定資源被佔用時間的長短。假設總資源是100,有一條慢SQL佔用了30的資源共計1分鐘。那麼在這1分鐘時間內,其餘SQL可以分配的資源總量就是70,如此循環,當資源分配完的時候,全部新的SQL執行將會排隊等待。
從應用的角度看:SQL執行時間長意味着等待,在OLTP應用當中,用戶的體驗較差算法

治理的優先級上sql

  1. master數據庫->slave數據庫數據庫

    • 目前數據庫基本上都是讀寫分離架構,讀在從庫(slave)上執行,寫在主庫(master)上執行。緩存

    • 因爲從庫的數據都是從主庫上覆制過去的,主庫等待較多的,會加大與從庫的複製時延。架構

  2. 執行次數多的SQL優先治理併發

  3. 若是有一類SQL高併發集中訪問某一張表,應當優先治理。分佈式

MySQL執行原理

綠色部分爲SQL實際執行部分,能夠發現SQL執行2大步驟:解析,執行。
以com_query爲例,dispatch_command會先調用alloc_query爲query buffer分配內存,以後調用解析
解析:詞法解析->語法解析->邏輯計劃->查詢優化->物理執行計劃
檢查是否存在可用查詢緩存結果,若是沒有或者緩存失效,則調用mysql_execute_command執行
執行:檢查用戶、表權限->表上加共享讀鎖->取數據到query cache->取消共享讀鎖
圖片描述函數

影響因素

如不考慮MySQL數據庫的參數以及硬件I/O的影響, 則影響SQL執行效率的因素主要是I/O和CPU的消耗量
總結:高併發

  1. 數據量:數據量越大須要的I/O次數越多

  2. 取數據的方式

    • 數據在緩存中仍是在磁盤上

    • 是否能夠經過索引快速尋址

  3. 數據加工的方式

    • 排序、子查詢等,須要先把數據取到臨時表中,再對數據進行加工

    • 增長了I/O,且消耗大量CPU資源

解決思路

  1. 將數據存放在更快的地方。

    • 若是數據量不大,變化頻率不高,但訪問頻率很高,此時應該考慮將數據放在應用端的緩存當中或者Redis這樣的緩存當中,以提升存取速度。若是數據不作過濾、關聯、排序等操做,僅按照key進行存取,且不考慮強一致性需求,也可考慮選用NoSQL數據庫。

  2. 適當合併I/O

    • 分別執行select c1 from t1與select c2 from t1,與執行select c1,c2 from t1相比,後者開銷更小。

    • 合併時也須要考慮執行時間的增長。

  3. 利用分佈式架構

    • 在面對海量的數據時,一般的作法是將數據和I/O分散到多臺主機上去執行。

案例與小技巧

OLTP環境下一般使用innodb引擎。索引使用b+tree.

主鍵

MyISAM引擎表的索引當中存的是指針,指向數據的地址。而innodb引擎當中主鍵存的是行數據。
所以絕大多數狀況下,經過主鍵取數據效率是最高的。
另外,一般咱們在建表的時候會指定一個自增整數列做爲主鍵,所以數據在存放時是按照id順序存放的。
也就是說,select c1 from t1 與 select c1 from t1 order by id 是一致的。

索引

向有索引的表當中插入、更新、刪除數據時,都須要對索引進行相應操做, 所以索引越多,寫表效率越低。
索引列的選擇須要注意列的離散度(惟一性)越高,越適合作索引。

create table t1 ( id int,
name varchar2(50),
tel int(11),
zipcode int(6),
city varchar2(10),
gender smallint(1)
);

上述表中,最不適合作索引的列就是gender。另外若是某列當中有不少的null值,也不適合作索引。
索引中存的是主鍵的值(表若是沒有設置主鍵,mysql會自動爲表設置一列隱藏數字列做爲主鍵。)
所以與順序排列的主鍵不一樣,查詢索引可能是隨機I/O。

複合索引

若是一列沒法作到惟一,能夠將幾列數據組合起來作成索引。這種狀況就是複合索引。
例如上表,咱們能夠創建一條複合索引

ALTER TABLE t1 ADD INDEX t1_tel_city_zipcode (tel,zipcode,city);

這時,咱們查詢條件包含以下條件,均可以利用索引:
tel
tel,zipcode
tel,zipcode,city
須要注意的是,若是條件是zipcode或者tel,city是沒法走索引的。
複合索引設計時,應考慮索引之間組合的順序,將離散度最高的列放在前面

索引列長度

InnoDB單列索引長度不能超過767bytes,複合索引是3072。

mysql> CREATE TABLE tb ( 
a varchar(255) DEFAULT NULL, 
b varchar(255) DEFAULT NULL, 
c varchar(255) DEFAULT NULL, 
d varchar(255) DEFAULT NULL, 
e varchar(255) DEFAULT NULL, 
KEY a (a,b,c,d,e) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 
ERROR 1071 (42000): Specified key was too long; max key length is 3072 bytes

咱們知道InnoDB一個page的默認大小是16k。因爲是Btree組織,要求葉子節點上一個page至少要包含兩條記錄(不然就退化鏈表了)。
因此一個記錄最多不能超過8k。又因爲InnoDB的聚簇索引結構,一個二級索引要包含主鍵索引,所以每一個單個索引不能超過4k (極端狀況,pk和某個二級索引都達到這個限制)。
因爲須要預留和輔助空間,扣掉後不能超過3500,取個「整數」就是(1024*3)。

單列索引限制

上面有提到單列索引限制767,原由是256×3-1。這個3是字符最大佔用空間(utf8)。可是在5.5之後,開始支持4個字節的uutf8。255×4>767, 因而增長了一個參數叫作 innodb_large_prefix
這個參數默認值是OFF。當改成ON時,容許列索引最大達到3072。
索引列的長度應考慮儘量短,以提升讀寫效率。基於此規則能夠引伸出
整型數值類型優於字符類型,例如手機號碼
日期字段,date類型,timestamp類型優於字符類型
字符集選擇
latin1 ->gbk ->utf8
對於某些很長,但又必須建索引的列,能夠考慮創建前綴索引。

利用索引

假設有一張500w的表,以自增列id 爲主鍵,取id爲400w到401w的數據,你會怎麼作?
一般的作法是使用limit m,n ,這裏提供一種思路:經過id定位到第400w條數據,再日後取1w條。

mysql> select id,c1 from t1 limit 4000000,10000;
......
| 4009998 | 4009998 |
| 4009999 | 4009999 |
| 4010000 | 4010000 |
+---------+---------+
10000 rows in set (0.84 sec)

mysql> reset query cache;
Query OK, 0 rows affected (0.00 sec)

mysql> select id,c1 from t1 where id > 4000000 limit 10000;
......
| 4009998 | 4009998 |
| 4009999 | 4009999 |
| 4010000 | 4010000 |
+---------+---------+
10000 rows in set (0.00 sec)

避免隱式轉化

where 條件時,變量的類型應當與列的類型匹配。若是不匹配,數據庫須要額外將變量類型轉換,稱爲隱式轉化。

mysql> desc t1;
+-------+--------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------+------+-----+---------+----------------+
| id | int(8) | NO | PRI | NULL | auto_increment |
| c1 | int(8) | YES | | NULL | |
+-------+--------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

mysql> select id,c1 from t1 where c1='4398724';
+---------+---------+
| id | c1 |
+---------+---------+
| 4398724 | 4398724 |
+---------+---------+
1 row in set (0.99 sec)

mysql> reset query cache;
Query OK, 0 rows affected (0.00 sec)

mysql> select id,c1 from t1 where c1=4398825;
+---------+---------+
| id | c1 |
+---------+---------+
| 4398825 | 4398825 |
+---------+---------+
1 row in set (0.98 sec)

同理
where子句的查詢條件裏有!=,MySQL將沒法使用索引。
where子句使用了Mysql函數的時候,索引將無效,好比:

select * from tb where left(name, 4) = 'xxx';

使用LIKE進行搜索匹配的時候,這樣索引是有效的:

select * from tbl1 where name like 'xxx%';

而like '%xxx%' 時索引無效

字段長度

MySQL數字分爲tinyint、smallint、int、bigint

  • bigint
    從 -2^63 (-9223372036854775808) 到 2^63-1 (9223372036854775807) 的整型數據(全部數字)。存儲大小爲 8 個字節。

  • int
    從 -2^31 (-2,147,483,648) 到 2^31 - 1 (2,147,483,647) 的整型數據(全部數字)。存儲大小爲 4 個字節。int 的 SQL-92 同義字爲 integer。

  • smallint
    從 -2^15 (-32,768) 到 2^15 - 1 (32,767) 的整型數據。存儲大小爲 2 個字節。

  • tinyint
    從 0 到 255 的整型數據。存儲大小爲 1 字節。

爲何要這麼複雜, 全部的數字類型都設置爲bigint不就能夠了麼?咱們來作一個實驗

create table t1 ( c1 smallint);
create table t2 ( c1 bigint);

delimiter $$  
drop procedure if exists wk;
create procedure wk()      
begin
declare i int;          
set i = 1;     
while i < 32767 do         
insert into t1 (c1) values (i);
insert into t2 (c1) values (i);
set i = i +1;
end while;
commit;
end $$ 
delimiter ;         
call wk();

select c1 from t1;
......
| 32765 |
| 32766 |
 +-------+
 32766 rows in set (0.01 sec)

select c1 from t2;
......
| 32765 |
| 32766 |
+-------+
32766 rows in set (0.02 sec)

能夠發現一樣的數據,從t2表中取出的耗時較長。由於咱們爲t2表設置了較寬的列長,意味着將數據swap進query cache須要更多的I/O。所以建議在設置字段寬度的時候,在保持長度夠用的狀況下,儘可能使用較短寬度的數據類型,以節省空間。同理,使用varchar類型替換char類型,並避免使用blob、clob這樣的長字段。

使用join替代in

SELECT * FROM customerinfo 
WHERE CustomerID NOT in (SELECT CustomerID FROM salesinfo );
SELECT * FROM customerinfo 
LEFT JOIN salesinfo 
ON customerinfo.CustomerID=salesinfo.CustomerID 
WHERE salesinfo.CustomerID IS NULL;

假設2張表CustomerID字段都有索引,使用join的效率要高於使用in,由於join不須要創建臨時表。

explain使用方法

explain就是SQL的執行計劃,經過執行計劃,咱們能夠了解sql的執行當中的一些細節。
使用方法爲在SQL語句前加explain
獲得結果以下:

mysql> explain select id,c1 from t1 where c1=4398825;
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
| 1 | SIMPLE | t1 | ALL | NULL | NULL | NULL | NULL | 4992210 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
1 row in set (0.00 sec)

各列功能以下:

  • id: 按照sql語法解析後分層後的編號,可能重複

  • select_type:

    • SIMPLE,簡單的select查詢,不使用union及子查詢

    • PRIMARY,最外層的select查詢

    • UNION,UNION 中的第二個或隨後的 select 查詢,不依賴於外部查詢的結果集

    • DEPENDENT UNION,UNION 中的第二個或隨後的 select 查詢,依賴於外部查詢的結果集

    • SUBQUERY,子查詢中的第一個 select 查詢,不依賴於外部查詢的結果集

    • DEPENDENT SUBQUERY,子查詢中的第一個 select 查詢,依賴於外部查詢的結果集

    • DERIVED,用於 from子句裏有子查詢的狀況。 MySQL會遞歸執行這些子查詢, 把結果放在臨時表裏。

    • UNCACHEABLE SUBQUERY,結果集不能被緩存的子查詢,必須從新爲外層查詢的每一行進行評估。

    • UNCACHEABLE UNION,UNION 中的第二個或隨後的 select 查詢,屬於不可緩存的子查詢

  • table:涉及的表,若是SQL中表有賦別名,這裏出現的是別名

  • type:

    • system,從系統表讀一行。這是const聯接類型的一個特例。

    • const,表最多有一個匹配行,它將在查詢開始時被讀取。由於僅有一行,在這行的列值可被優化器剩餘部分認爲是常數。const表很快,由於它們只讀取一次!

    • eq_ref,查詢條件爲等於

    • ref,條件查詢不等於

    • ref_or_null,同ref(條件查詢),包含NULL值的行。

    • index_merge,索引聯合查詢

    • unique_subquery,利用惟一索引進行子查詢

    • index_subquery,用非惟一索引進行子查詢

    • range,索引範圍掃描

    • index,索引全掃描

    • ALL,全表掃描。

  • possible_keys:可能使用的索引

  • key:sql中使用的索引

  • key_len:索引長度

  • ref:使用哪一個列或常數與key一塊兒從表中選擇行。

  • rows:顯示MYSQL執行查詢的行數,簡單且重要,數值越大越很差,說明沒有用好索引

  • Extra:該列包含MySQL解決查詢的詳細信息。

    • Distinct,去重,返回第一個知足條件的值

    • Not exists 使用not exists查詢

    • Range checked for each record,有索引,但索引選擇率很低

    • Using filesort,有序查詢

    • Using index,索引全掃描

    • Using index condition,索引查詢

    • Using temporary,臨表表檢索

    • Using where,where條件查詢

    • Using sort_union,有序合併查詢

    • Using union,合併查詢

    • Using intersect,索引交叉合併

    • Impossible WHERE noticed after reading const tables,讀取const tables,查詢結果爲空

    • No tables used,沒有使用表

    • Using join buffer (Block Nested Loop),使用join buffer(BNL算法)

    • Using MRR(Multi-Range Read ) 使用輔助索引進行多範圍讀

相關文章
相關標籤/搜索