分析:
1、至少跑一天,看看生產慢SQL情況
2、開啓慢查詢日誌,設置閾值,比如超過5秒的就是慢SQL,並將它抓取出來。
3、explain+慢SQL分析
4、show profile(分析效果與explain差不多)
5、運維經理 or DBA 進行SQL數據庫服務器的參數調優。
小表驅動大表
即小的數據集驅動大的數據集
in:
select * from A where id in (select id from B)
這個語句,等價於先查詢B表,再查詢A表。小表應該是B,大表應該是A。B驅動A
exists:
select * from A where exists(select 1 from B where B.id = A.id)
現從A做一個查詢,之後在查詢B表。
意思是查看A表記錄是否與B表滿足 B.id = A.id,如果是,則返回一條記錄;如果否,則不返回。
這個語句,先查詢A,再查詢B。因此,A應該是小表。B應該是大表。 A驅動B
select ... from table where exists(subquery)
該語句理解爲:將主查詢的數據,放到子查詢中做條件驗證,根據驗證結果(true 或者Flase)來決定主查詢的數據結果是否予以保留。
hint:
1、exists(subquery)只返回true 或者Flase,因此子查詢中select 1 或者select 沒有區別。
2、exists子查詢實際上經過了優化而不是我們理解的逐條對比。
3、exists子查詢往往也可以用條件表達式、其他子查詢後者join來代替,何種最優需要具問具分。
order by(討論會不會產生filesort):
order by儘量使用index而不是filesort排序。
建表:
age ,birth兩個字段。 建立索引age,birth
1、
select * from tbla where age > 20 order by age;
以前關注用沒用索引,現在關注order by後面會不會產生filesort。這句話不會差生filesort。
2、
select * from tbla where age > 20 order by age,birth;order by age,birth
用到了索引排序,因此不會產生filesort
3、
select * from tbla where age > 20 order by birth;
產生filesort,order by後面缺少帶頭大哥,因爲age所引用到了但是是一個範圍,因此不能“從一樓到二樓”。
4、
select * from tbla where age > 20 order by birth,age;
產生filesort,這就不像where後面的and,順序反了可以優化,order by後面順序反了不會優化。
5、
select * from tbla order by birth;
產生filesort,帶頭大哥沒有
6、
select * from tbla where birth > '2020-04-23 00:00:00' order by birth;
產生filesort,帶頭大哥沒有
7、
select * from tbla where birth > '2020-04-23 00:00:00' order by age;
不會產生filesort,order by後面用到了索引排序。
8、
select * from tbla order by age ASC,birth DESC;
帶頭大哥在,順序和索引相同。但仍會產生filesort,要麼同升,要麼同降。
總結:
MySQL支持兩種方式排序:index和filesort。index效率要高。
order by滿足下列兩個情況會用到索引排序:
1、order by後面的語句使用索引最左前綴匹配原則。
2、使用where子句與order by子句條件列組合滿足最左前綴匹配原則
如果用不到index排序,filesort排序會有兩種算法:
雙路排序:MySQL4.1之前使用雙路排序,兩次掃描磁盤得到數據。
單路排序:從磁盤讀取所有列,按照order by列在buffer對他們進行排序,然後掃描排序後的列表進行輸出,它的效率更快些,避免了第二次讀取數據。並且把隨機IO變成順序IO。
單路總體而言好於雙路。
提高order by速度:
1、order by時候儘量不要用select *。
2、嘗試提高sort_buffer_size
3、嘗試提高max_length_for_sort_data
group by:
group by實質是線排序後分組,遵循索引建立的最左前綴原則
where 優先於HAVING。能寫where就不要用HAVING。
慢查詢日誌:
MySQL慢查詢日誌是MySQL提供的一種日誌記錄,它用來記錄在MySQL中響應時間超過閾值的語句,具體指運行時間超過long_query_time值的SQL,則會被記錄到慢查詢日誌中。
long_query_time默認10秒。
默認MySQL沒有開啓慢查詢日誌。沒事不要開啓,開啓會影響性能。
查看開啓:SHOW VARIABLES LIKE ‘%slow_query_log%’
開啓:
set global slow_query_log = 1 (僅對當前數據庫有效,重啓失效)
查看閾值:
SHOW global VARIABLES LIKE ‘%long_query_time%’
設置閾值:
set global long_query_time = 10(表示10秒)
可使用select sleep(6)驗證。
mysqldumpslow:官方提供的日誌分析工具
得到返回記錄最多的10個SQL:
mysqldumpslow -s r -t 10 /var/lib/mysql/XXXXX-slow.log
得到訪問次數最多的10個SQL:mysqldumpslow -s c -t 10 /var/lib/mysql/XXXXX-slow.log
得到按時間排序前10條裏面含有左連接的查詢語句:mysqldumpslow -s t -t 10 -g “left join” /var/lib/mysql/XXXXX-slow.log
存儲過程:
DELIMITER $$
意思是將結束符改爲$$。
show profile:
MySQL調優思路:
step1:察覺到系統變慢
step2:給SQL跑一圈(重現故障)利用慢查詢日誌
step3:採取explain分析具體SQL
step4:第三部沒擺平,使用show profile
show profile是MySQL提供可以用來分析當前會話中語句執行的資源消耗情況。可以用於SQL調優的測量。
默認情況下是關閉狀態。
查看show profile是否開啓:
show variables like ‘profiling’;
開啓show profile:
set profiling=on;
開啓之後,show profile會在後臺自動抓取查詢信息。
查看所有執行過的SQL。
show profiles;
診斷SQL:
show profile cpu,block io for query Query_ID(show profiles裏面的id);
這句話診斷出一條SQL生命週期裏發生的所有事件以及發生的時長。
上述SQL中,cpu和block IO是可替換項:可添加一個或者多個,中間用逗號隔開。
type:
| ALL --顯示所有的開銷信息
| BLOCK IO --顯示塊IO相關開銷
| CONTEXT SWITCHES --上下文切換相關開銷
| CPU --顯示CPU相關開銷信息
| IPC --顯示發送和接收相關開銷信息
| MEMORY --顯示內存相關開銷信息
| PAGE FAULTS --顯示頁面錯誤相關開銷信息
| SOURCE --顯示和Source_function,Source_file,Source_line相關的開銷信息
| SWAPS --顯示交換次數相關開銷的信息
在status狀態下出現以下item說明有問題:
1、converting HEAP to MyISAM 查詢結果太大了,內存都不夠用往磁盤上搬了
2、creating tmp table 創建臨時表(拷貝數據到臨時表,用完再刪除)
3、copying to tmp table on disk 把內存中臨時表複製到磁盤,危險!!
4、locked
MySQL鎖:
對數據操作類型分:(讀鎖/寫鎖)
讀鎖:針對同一份數據,多個讀操作可以同時進行而不會互相影響。
寫鎖(排它鎖):當前寫操作沒有完成前,它會阻斷其他寫鎖和讀鎖。
對鎖的粒度分:
表鎖:
開銷小、加鎖快,無死鎖,鎖的粒度大,發生鎖衝突的概率最高,併發度最低。
建表:
create table mylock(
id int primary key auto_increment,
name varchar(20),
) engine myisam;
插入數據:
insert into mylock(name) values ('a'),('b'),('c'),('d'),('e');
開兩個終端(鏈接數據庫):
手動增加表鎖:
lock table 表名字 read/write , 表名字2 read/write , 其他;
解鎖:
unlock tables;(所有表全解鎖了)
查看錶是否加過鎖:
show open tables;
database tables in_use name_locked
是否加鎖0/1
如何分析表鎖定:
show status like ‘table%’;
table_locks_immediate:產生表級鎖定次數,表示可以立即獲取鎖的查詢次數,每立即獲得鎖值加1。
table_locks_waited:出現表級鎖定爭用而發生的等待次數,此值高說明存在着較爲嚴重的表級鎖爭用情況。
MyISAM讀寫鎖調度是寫鎖優先(即讀鎖寫鎖競爭時,寫鎖能搶到),由於寫鎖特性,寫鎖優先會導致其他進程讀寫永遠阻塞。
讀鎖:
session1對錶mylock加讀鎖。
session1可以讀mylock,不能寫mylock,不能讀寫其他未鎖定的表。
session2可以讀mylock,寫mylock時會進入阻塞狀態,直到session釋放鎖才執行寫操作。可以讀寫其他未鎖定的表。
寫鎖:
session1對錶mylock加寫鎖。
session1可以讀寫mylock,不能讀寫其他未加鎖的表
session2可以讀寫其他未加鎖的表,讀寫mylock會阻塞,等待session釋放鎖。
簡而言之(針對session2):讀鎖會阻塞寫,但是不會阻塞讀。而寫鎖則會把讀和寫都阻塞。
行鎖(innodb默認加鎖?):
開銷大、加鎖慢、會出現死鎖;鎖粒度最小,發生鎖衝突概率最低,併發度也高。InnoDB和MyISAM區別:1、InnoDB支持事務。2、InnoDB採用了行級鎖。
課堂實驗:
建表:
create table test_innodb_lock(a int(11) , b varchar(16))engine = innodb;
insert into test_innodb_lock values(1,'b2'),(3,'3'),(4,'4000'),(5,'5000'),(6,'6000'),(7,'7000'),(8,'8000'),(9,'9000'),(1,'b1');
建索引:
create index test_innodb_a_ind on test_innodb_lock(a);
create index test_innodb_lock_b_ind on test_innodb_lock(b);
設置自動提交爲0;
set autocommit = 0;
在session1中更改數據(未提交),session1自己看到的數據更改了,但是session2看得的數據是原來的數據。
兩個session更改同一行數據:
兩個session更改不同行數據:
可自由更改無影響。
索引建立之後使用不當導致行鎖升級爲表鎖
case:
在session1中執行下列語句:
update test_innodb_lock set a = 10 where b = 4000;
由於b是varchar而where後面沒有加單引號,因此導致索引失效。此時,行鎖變表鎖。seesion2中再去寫會被阻塞。
間隙鎖:
當我們用範圍條件而不是相等條件檢索數據時,會給這個範圍內的所有鍵值加鎖。
例如:
表如圖所示:
session1執行:
update test_innodb_lock set b = ‘0987’ where a >1 and a<6;
此時,未提交,MySQL會將a>1 ,<6的鍵值全上鎖。(即使沒有a=2的記錄)。
seesion2執行:
insert into test_innodb_lock values(2,‘2000’);
commit;
由於seesion1沒有commit,所以seesion2執行此句後會阻塞,直到seesion1 commit之後釋放鎖。
如何鎖定一行:
seesion1執行:
select * from test_innodb_lock where a = 8 for update;
手工爲a=8這條記錄上鎖。
session2更改a=8這行數據會阻塞,直到session1執行commit。
行鎖問題排查:
show status like ‘innodb_row_lock%’;