注:因爲在MySQL平常查詢中,查詢類型的語句佔慢sql的大部分,所以本文僅針對query類型的sql進行闡述,modify類型會單獨分析。mysql
從數據庫角度看:每一個SQL執行都須要消耗必定I/O資源,SQL執行的快慢,決定資源被佔用時間的長短。假設總資源是100,有一條慢SQL佔用了30的資源共計1分鐘。那麼在這1分鐘時間內,其餘SQL可以分配的資源總量就是70,如此循環,當資源分配完的時候,全部新的SQL執行將會排隊等待。
從應用的角度看:SQL執行時間長意味着等待,在OLTP應用當中,用戶的體驗較差算法
治理的優先級上sql
master數據庫->slave數據庫數據庫
目前數據庫基本上都是讀寫分離架構,讀在從庫(slave)上執行,寫在主庫(master)上執行。緩存
因爲從庫的數據都是從主庫上覆制過去的,主庫等待較多的,會加大與從庫的複製時延。架構
執行次數多的SQL優先治理併發
若是有一類SQL高併發集中訪問某一張表,應當優先治理。分佈式
綠色部分爲SQL實際執行部分,能夠發現SQL執行2大步驟:解析,執行。
以com_query爲例,dispatch_command會先調用alloc_query爲query buffer分配內存,以後調用解析
解析:詞法解析->語法解析->邏輯計劃->查詢優化->物理執行計劃
檢查是否存在可用查詢緩存結果,若是沒有或者緩存失效,則調用mysql_execute_command執行
執行:檢查用戶、表權限->表上加共享讀鎖->取數據到query cache->取消共享讀鎖函數
如不考慮MySQL數據庫的參數以及硬件I/O的影響, 則影響SQL執行效率的因素主要是I/O和CPU的消耗量
總結:高併發
數據量:數據量越大須要的I/O次數越多
取數據的方式
數據在緩存中仍是在磁盤上
是否能夠經過索引快速尋址
數據加工的方式
排序、子查詢等,須要先把數據取到臨時表中,再對數據進行加工
增長了I/O,且消耗大量CPU資源
將數據存放在更快的地方。
若是數據量不大,變化頻率不高,但訪問頻率很高,此時應該考慮將數據放在應用端的緩存當中或者Redis這樣的緩存當中,以提升存取速度。若是數據不作過濾、關聯、排序等操做,僅按照key進行存取,且不考慮強一致性需求,也可考慮選用NoSQL數據庫。
適當合併I/O
分別執行select c1 from t1與select c2 from t1,與執行select c1,c2 from t1相比,後者開銷更小。
合併時也須要考慮執行時間的增長。
利用分佈式架構
在面對海量的數據時,一般的作法是將數據和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這樣的長字段。
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就是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 ) 使用輔助索引進行多範圍讀