MySQL的SQL優(yōu)化
通過 show status 命令了解各種 SQL 的執(zhí)行頻率
Com_select:執(zhí)行 select 操作的次數(shù)总处,一次查詢只累加 1基茵。
Com_insert:執(zhí)行 INSERT 操作的次數(shù),對于批量插入的 INSERT 操作,只累加一次。
Com_update:執(zhí)行 UPDATE 操作的次數(shù)。
Com_delete:執(zhí)行 DELETE 操作的次數(shù)亿鲜。
上面這些參數(shù)對于所有存儲引擎的表操作都會進(jìn)行累計。下面這幾個參數(shù)只是針對
InnoDB 存儲引擎的冤吨,累加的算法也略有不同蒿柳。
Innodb_rows_read:select 查詢返回的行數(shù)。
Innodb_rows_inserted:執(zhí)行 INSERT 操作插入的行數(shù)漩蟆。
Innodb_rows_updated:執(zhí)行 UPDATE 操作更新的行數(shù)垒探。
Innodb_rows_deleted:執(zhí)行 DELETE 操作刪除的行數(shù)。
通過 EXPLAIN 分析低效 SQL 的執(zhí)行計劃
mysql> explain select sum(moneys) from sales a,company b where a.company_id = b.id and a.year
= 2006\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: a
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1000
Extra: Using where
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: b
type: ref
possible_keys: ind_company_id
key: ind_company_id
key_len: 5
ref: sakila.a.company_id
rows: 1
Extra: Using where; Using index
2 rows in set (0.00 sec)
select_type:表示 SELECT 的類型怠李,常見的取值有 SIMPLE(簡單表圾叼,即不使用表連接或者子查詢)、PRIMARY(主查詢捺癞,即外層的查詢)夷蚊、UNION(UNION 中的第二個或者后面的查詢語句)、SUBQUERY(子查詢中的第一個 SELECT)等髓介。
table:輸出結(jié)果集的表惕鼓。
type:表示表的連接類型,性能由好到差的連接類型為 system(表中僅有一行版保,即常量表)呜笑、const(單表中最多有一個匹配行夫否,例如 primary key 或者 unique index)彻犁、eq_ref(對于前面的每一行,在此表中只查詢一條記錄凰慈,簡單來說汞幢,就是多表連接中使用primary key或者unique index)、ref (與eq_ref類似微谓,區(qū)別在于不是使用primarykey 或者 unique index森篷,而是使用普通的索引)输钩、ref_or_null(與 ref 類似,區(qū)別在于條件中包含對 NULL 的查詢)index_merge(索引合并優(yōu)化)仲智、unique_subquery(in的后面是一個查詢主鍵字段的子查詢)买乃、index_subquery (與 unique_subquery 類似,區(qū)別在于 in 的后面是查詢非唯一索引字段的子查詢)钓辆、range(單表中的范圍查詢)剪验、index(對于前面的每一行,都通過查詢索引來得到數(shù)據(jù))前联、all (對于前面的每一行功戚,都通過全表掃描來得到數(shù)據(jù))。
possible_keys:表示查詢時似嗤,可能使用的索引啸臀。
key:表示實(shí)際使用的索引。
key_len:索引字段的長度烁落。
rows:掃描行的數(shù)量乘粒。
Extra:執(zhí)行情況的說明和描述。
在上面的例子中伤塌,已經(jīng)可以確認(rèn)是對 a 表的全表掃描導(dǎo)致效率的不理想谓厘,那么對 a 表的year 字段創(chuàng)建索引,具體如下:
mysql> create index ind_sales2_year on sales2(year);
Query OK, 1000 rows affected (0.03 sec)
Records: 1000 Duplicates: 0 Warnings: 0
mysql> explain select sum(moneys) from sales2 a,company2 b where a.company_id = b.id and
a.year = 2006\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: a
type: ref
possible_keys: ind_sales2_year
key: ind_sales2_year
key_len: 2
ref: const
rows: 1
Extra: Using where
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: b
type: ref
possible_keys: ind_company2_id
key: ind_company2_id
key_len: 5
ref: sakila.a.company_id
rows: 1
Extra: Using where; Using index
2 rows in set (0.00 sec)
可以發(fā)現(xiàn)建立索引后對 a 表需要掃描的行數(shù)明顯減少(從 1000 行減少到 1 行)寸谜,可見索引的使用可以大大提高數(shù)據(jù)庫的訪問速度竟稳,尤其在表很龐大的時候這種優(yōu)勢更為明顯。
索引問題
MySQL 中索引的存儲類型目前只有兩種(BTREE 和 HASH)熊痴,具體和表的存儲引擎相關(guān):MyISAM 和 InnoDB 存儲引擎都只支持 BTREE 索引他爸;MEMORY/HEAP 存儲引擎可以支持 HASH和 BTREE 索引
MySQL 目前不支持函數(shù)索引,但是能對列的前面某一部分進(jìn)索引
mysql> create index ind_company2_name on company2(name(4));
Query OK, 1000 rows affected (0.03 sec)
Records: 1000 Duplicates: 0 Warnings: 0
使用索引
1.對于創(chuàng)建的多列索引果善,只要查詢的條件中用到了最左邊的列诊笤,索引一般就會被使用,舉例說明如下巾陕。
mysql> create index ind_sales2_companyid_moneys on sales2(company_id,moneys);
Query OK, 1000 rows affected (0.03 sec)
Records: 1000 Duplicates: 0 Warnings: 0
--然后按 company_id 進(jìn)行表查詢讨跟,具體如下:
mysql> explain select * from sales2 where company_id = 2006\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sales2
type: ref
possible_keys: ind_sales2_companyid_moneys
key: ind_sales2_companyid_moneys
key_len: 5
ref: const
rows: 1
Extra: Using where
1 row in set (0.00 sec)
--可以發(fā)現(xiàn)即便 where 條件中不是用的 company_id 與 moneys 的組合條件,索引仍然能用到鄙煤,這就是索引的前綴特性晾匠。但是如果只按 moneys 條件查詢表,那么索引就不會被用到梯刚,具體如下:
mysql> explain select * from sales2 where moneys = 1\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sales2
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1000
Extra: Using where
1 row in set (0.00 sec)
2.對于使用 like 的查詢凉馆,后面如果是常量并且只有%號不在第一個字符,索引才可能會被使用,來看下面兩個執(zhí)行計劃:
mysql> explain select * from company2 where name like '%3'\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: company2
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1000
Extra: Using where
1 row in set (0.00 sec)
mysql> explain select * from company2 where name like '3%'\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: company2
type: range
possible_keys: ind_company2_name
key: ind_company2_name
key_len: 11
ref: NULL
rows: 103
Extra: Using where
1 row in set (0.00 sec)
3.如果對大的文本進(jìn)行搜索澜共,使用全文索引而不用使用 like ‘%…%’向叉。
4.如果列名是索引,使用 column_name is null 將使用索引嗦董。如下例中查詢 name 為 null的記錄就用到了索引:
mysql> explain select * from company2 where name is null\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: company2
type: ref
possible_keys: ind_company2_name
key: ind_company2_name
key_len: 11
ref: const
rows: 1
Extra: Using where
1 row in set (0.00 sec)
存在索引但不使用索引
1.如果 MySQL 估計使用索引比全表掃描更慢母谎,則不使用索引。例如如果列
key_part1 均勻分布在 1 和 100 之間京革,下列查詢中使用索引就不是很好:
SELECT * FROM table_name where key_part1 > 1 and key_part1 < 90;
2.如果使用 MEMORY/HEAP 表并且 where 條件中不使用“=”進(jìn)行索引列销睁,那么
不會用到索引。heap 表只有在“=”的條件下才會使用索引存崖。
3.用 or分割開的條件冻记,如果 or前的條件中的列有索引,而后面的列中沒有索引来惧,
那么涉及到的索引都不會被用到冗栗,例如:
mysql> show index from sales\G;
*************************** 1. row ***************************
Table: sales
Non_unique: 1
Key_name: ind_sales_year
Seq_in_index: 1
Collation: A
Cardinality: NULL
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
1 row in set (0.00 sec)
Column_name: year
--從上面可以發(fā)現(xiàn)只有 year 列上面有索引,來看如下的執(zhí)行計劃:
mysql> explain select * from sales where year = 2001 or country = 'China'\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sales
type: ALL
possible_keys: ind_sales_year
key: NULL
key_len: NULL
ref: NULL
rows: 12
Extra: Using where
1 row in set (0.00 sec)
4.如果不是索引列的第一部分供搀,如下例子:
mysql> explain select * from sales2 where moneys = 1\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sales2
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1000
Extra: Using where
1 row in set (0.00 sec)
--可見雖然在 money 上面建有復(fù)合索引隅居,但是由于 money 不是索引的第一列,那么在查詢中這個索引也不會被 MySQL 采用葛虐。
5.如果 like 是以%開始
6.如果列類型是字符串胎源,那么一定記得在 where 條件中把字符常量值用引號引
起來,否則的話即便這個列上有索引屿脐,MySQL 也不會用到的涕蚤,因為,MySQL 默認(rèn)把輸入的常量值進(jìn)行轉(zhuǎn)換以后才進(jìn)行檢索的诵。如下面的例子中company2表中的name字段是字符型的万栅,但是 SQL 語句中的條件值 294 是一個數(shù)值型值,因此即便在 name 上有索引西疤,MySQL 也不能正確地用上索引烦粒,而是繼續(xù)進(jìn)行全表掃描。
mysql> explain select * from company2 where name = 294\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: company2
type: ALL
possible_keys: ind_company2_name
key: NULL
key_len: NULL
ref: NULL
rows: 1000
Extra: Using where
1 row in set (0.00 sec)
mysql> explain select * from company2 where name = '294'\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: company2
type: ref
possible_keys: ind_company2_name
key: ind_company2_name
key_len: 23
ref: const
rows: 1
Extra: Using where
1 row in set (0.00 sec)
查看索引使用情況
如果索引正在工作代赁,Handler_read_key 的值將很高扰她,這個值代表了一個行被索引值讀的次數(shù),很低的值表明增加索引得到的性能改善不高芭碍,因為索引并不經(jīng)常使用徒役。
Handler_read_rnd_next 的值高則意味著查詢運(yùn)行低效,并且應(yīng)該建立索引補(bǔ)救豁跑。這個值的含義是在數(shù)據(jù)文件中讀下一行的請求數(shù)廉涕。如果正進(jìn)行大量的表掃描泻云,Handler_read_rnd_next 的值較高艇拍,則通常說明表索引不正確或?qū)懭氲牟樵儧]有利用索引狐蜕,具體如下。
mysql> show status like 'Handler_read%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Handler_read_first | 0 |
| Handler_read_key | 5 |
| Handler_read_next | 0 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 2055 |
+-----------------------+-------+
6 rows in set (0.00 sec)
兩個簡單實(shí)用的優(yōu)化方法
1.定期分析表和檢查表
分析的結(jié)果將可以使得系統(tǒng)得到準(zhǔn)確的統(tǒng)計信息卸夕,使得 SQL 能夠生成正確的執(zhí)行計劃层释。
--分析表的語法如下:
ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...
mysql> analyze table sales;
+--------------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+--------------+---------+----------+----------+
| sakila.sales | analyze | status | OK |
+--------------+---------+----------+----------+
1 row in set (0.00 sec)
--檢查表的語法如下:
CHECK TABLE tbl_name [, tbl_name] ... [option] ... option = {QUICK | FAST | MEDIUM | EXTENDED
| CHANGED}
--檢查表的作用是檢查一個或多個表是否有錯誤。CHECK TABLE對MyISAM和InnoDB表有作用快集。對于 MyISAM 表贡羔,關(guān)鍵字統(tǒng)計數(shù)據(jù)被更新,例如:
mysql> check table sales;
+--------------+-------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+--------------+-------+----------+----------+
| sakila.sales | check | status | OK |
+--------------+-------+----------+----------+
1 row in set (0.00 sec)
CHECK TABLE 也可以檢查視圖是否有錯誤个初,比如在視圖定義中被引用的表已不存在乖寒,舉例如下
--1)首先我們創(chuàng)建一個視圖
mysql> create view sales_view3 as select * from sales3;
Query OK, 0 rows affected (0.00 sec)
--2)然后 CHECK 一下該視圖,發(fā)現(xiàn)沒有問題院溺。
mysql> check table sales_view3;
+--------------------+-------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+--------------------+-------+----------+----------+
| sakila.sales_view3 | check | status | OK |
+--------------------+-------+----------+----------+
1 row in set (0.00 sec)
--3)現(xiàn)在刪除掉視圖依賴的表
mysql> drop table sales3;
Query OK, 0 rows affected (0.00 sec)
--4)再來 CHECK 一下剛才的視圖楣嘁,發(fā)現(xiàn)報錯了
mysql> check table sales_view3\G;
*************************** 1. row ***************************
Table: sakila.sales_view3
Op: check
Msg_type: error
Msg_text: View 'sakila.sales_view3' references invalid table(s) or column(s) or function(s)
or definer/invoker of view lack rights to use them
1 row in set (0.00 sec)
2.定期優(yōu)化表
優(yōu)化表的語法如下:
OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...
如果已經(jīng)刪除了表的一大部分,或者如果已經(jīng)對含有可變長度行的表(含有 VARCHAR珍逸、BLOB 或 TEXT 列的表)進(jìn)行了很多更改逐虚,則應(yīng)使用 OPTIMIZE TABLE 命令來進(jìn)行表優(yōu)化。這個命令可以將表中的空間碎片進(jìn)行合并谆膳,并且可以消除由于刪除或者更新造成的空間浪費(fèi)叭爱,但OPTIMIZE TABLE 命令只對 MyISAM、BDB 和 InnoDB 表起作用漱病。
mysql> optimize table sales;
+--------------+----------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+--------------+----------+----------+----------+
| sakila.sales | optimize | status | OK |
+--------------+----------+----------+----------+
1 row in set (0.00 sec)
常用 SQL 的優(yōu)化
優(yōu)化 GROUP BY 語句
如果查詢包括 GROUP BY 但用戶想要避免排序結(jié)果的消耗买雾,則可以指定 ORDER BY NULL
禁止排序,如下面的例子:
mysql> explain select id,sum(moneys) from sales2 group by id\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sales2
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1000
Extra: Using temporary; Using filesort
1 row in set (0.00 sec)
mysql> explain select id,sum(moneys) from sales2 group by id order by null\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sales2
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1000
Extra: Using temporary
1 row in set (0.00 sec)
從上面的例子可以看出第一個SQL語句需要進(jìn)行“filesort”杨帽,而第二個SQL由于ORDER BY NULL不需要進(jìn)行“filesort”凝果,而 filesort 往往非常耗費(fèi)時間。
優(yōu)化 ORDER BY 語句:
在某些情況中睦尽,MySQL 可以使用一個索引來滿足 ORDER BY 子句器净,而不需要額外的排序。WHERE 條件和 ORDER BY 使用相同的索引当凡,并且 ORDER BY 的順序和索引順序相同山害,并且ORDER BY 的字段都是升序或者都是降序。
例如沿量,下列 SQL 可以使用索引浪慌。
SELECT * FROM t1 ORDER BY key_part1,key_part2,... ;
SELECT * FROM t1 WHERE key_part1=1 ORDER BY key_part1 DESC, key_part2 DESC;
SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 DESC;
但是在以下幾種情況下則不使用索引:
SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC;
--order by 的字段混合 ASC 和 DESC
SELECT * FROM t1 WHERE key2=constant ORDER BY key1朴则;
--用于查詢行的關(guān)鍵字與 ORDER BY 中所使用的不相同
SELECT * FROM t1 ORDER BY key1, key2权纤;
--對不同的關(guān)鍵字使用 ORDER BY:
優(yōu)化嵌套查詢
有些情況下,子查詢可以被更有效率的連接(JOIN)替代
mysql> explain select * from sales2 where company_id not in ( select id from
company2 )\G;
*************************** 1. row ***************************
id: 1
select_type: PRIMARY
table: sales2
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1000
Extra: Using where
*************************** 2. row ***************************
id: 2
select_type: DEPENDENT SUBQUERY
table: company2
type: index_subquery
possible_keys: ind_company2_id
key: ind_company2_id
key_len: 5
ref: func
rows: 2
Extra: Using index
2 rows in set (0.00 sec)
如果使用連接(JOIN)來完成這個查詢工作,速度將會快很多汹想。尤其是當(dāng) company2 表
中對 id 建有索引的話外邓,性能將會更好,具體查詢?nèi)缦拢?/p>
mysql> explain select * from sales2 left join company2 on sales2.company_id =
company2.id where sales2.company_id is null\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sales2
type: ref
possible_keys: ind_sales2_companyid_moneys
key: ind_sales2_companyid_moneys
key_len: 5
ref: const
rows: 1
Extra: Using where
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: company2
type: ref
possible_keys: ind_company2_id
key: ind_company2_id
key_len: 5
ref: sakila.sales2.company_id
rows: 1
Extra:
2 rows in set (0.00 sec)
連接(JOIN)之所以更有效率一些古掏,是因為 MySQL 不需要在內(nèi)存中創(chuàng)建臨時表來完成這
個邏輯上的需要兩個步驟的查詢工作损话。
MySQL 如何優(yōu)化 OR 條件
對于含有 OR 的查詢子句,如果要利用索引槽唾,則 OR 之間的每個條件列都必須用到索引丧枪;
如果沒有索引,則應(yīng)該考慮增加索引庞萍。
例如拧烦,首先使用 show index 命令查看表 sales2 的索引,可知它有 3 個索引钝计,在 id恋博、year
兩個字段上分別有 1 個獨(dú)立的索引,在 company_id 和 year 字段上有 1 個復(fù)合索引葵蒂。
--然后在兩個獨(dú)立索引上面做 OR 操作交播,具體如下:
mysql> explain select * from sales2 where id = 2 or year = 1998\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sales2
type: index_merge
possible_keys: ind_sales2_id,ind_sales2_year
key: ind_sales2_id,ind_sales2_year
key_len: 5,2
ref: NULL
rows: 2
Extra: Using union(ind_sales2_id,ind_sales2_year); Using where
1 row in set (0.00 sec)
可以發(fā)現(xiàn)查詢正確的用到了索引,并且從執(zhí)行計劃的描述中践付,發(fā)現(xiàn) MySQL 在處理含有 OR
字句的查詢時秦士,實(shí)際是對 OR 的各個字段分別查詢后的結(jié)果進(jìn)行了 UNION。
但是當(dāng)在建有復(fù)合索引的列company_id 和 moneys上面做 OR 操作的時候永高,卻不能用到索引隧土,具體結(jié)果如下:
mysql> explain select * from sales2 where company_id = 3 or moneys = 100\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sales2
type: ALL
possible_keys: ind_sales2_companyid_moneys
key: NULL
key_len: NULL
ref: NULL
rows: 1000
Extra: Using where
1 row in set (0.00 sec)