慢查詢的分析以及MYSQL中常用的優化方法


一條SQL執行很慢?

1.執行的時候遇到行鎖,表鎖

2.沒有建索引,或者建了索引沒有用到,需要去分析

        怎么判斷一個 mysql 中 select 語句是否使用了索引,可以在 select 語句前加上 explain,比如 explain select * from tablename;返回的一列中,若列名為 key 的那列為 null,則沒有使用索引,若不為 null,則返回實際使用的索引名。讓 select 強制使用索引的語法:select * from tablename from index(index_name);

3.數據庫在刷新臟頁,例如redolog寫滿了需要同步到磁盤。

 

MySQL 數據庫有一個“慢查詢日志”功能,用來記錄查詢時間超過某個設定值的SQL。

慢查詢日志功能開啟:

slow_query_log :是否開啟慢查詢日志功能(必填)

long_query_time :超過設定值,將被視作慢查詢,並記錄至慢查詢日志文件中(必填)

log-slow-queries :慢查詢日志文件(不可填),自動在 \data\ 創建一個 [hostname]-slow.log 文件

配置:

set global slow_query_log = ON;   # 開啟這個功能
 
set GLOBAL long_query_time = 1;# 設置查詢“超時”時間
當我們開啟這個功能時候每一條sql語句如果執行時間超過了設置的時間,就會被寫入到日志文件里面
在定位到我們有問題的sql文件以后:

如何對我們的sql語句進行優化呢

1.選擇最適用的字段類型(數據庫中的表越小,在它上面執行的查詢也就會越快)

比如一個字符串,能夠確定長度,就用char(6)而不要用char(255)這樣給數據庫增加不必要的空間,還有varchar長度是動態可變的(它更節省空間),char的效率比varchar快。

高重復的字段將其轉換為數值類型。數值類型的處理比文本類型快了很多。

對於字段比較多的表,如果有些字段的使用頻率很低,可以將這些字段分離出來形成新表。因為當一個表的數據量很大時,會由於使用頻率低的字段的存在而變慢。

2.使用連接(JOIN)來代替子查詢(Sub-Queries)

  連接(JOIN)之所以更有效率一些,是因為MySQL不需要在內存中創建臨時表來完成這個邏輯上的需要兩個步驟的查詢工作。

  但是大量的使用join也不是好的辦法:

  很多高性能的應用都會對關聯查詢進行分解,就是可以對每一個表進行一次單表查詢,然后將查詢結果在應用程序中進行關聯,很多場景下這樣會更高效。

3.使用UNION來替換手動創建臨時表

4.多用事務:

要把某個數據同時插入兩個相關聯的表中,可能會出現這樣的情況:第一個表中成功更新后,數據庫突然出現意外狀況,造成第二個表中的操作沒有完成,這樣,就會造成數據的不完整,甚至會破壞數據庫中的數據。

5.使用外鍵來保證數據的關聯性。

外鍵可以保證每一條銷售記錄都指向某一個存在的客戶。在這里,外鍵可以把customerinfo表中的customerid映射到salesinfo表中customerid,任何一條沒有合法customerid的記錄都不會被更新或插入到salesinfo中。

CREATE  TABLE   customerinfo( customerid   int primary key) engine = innodb;

CREATE  TABLE   salesinfo( salesid int not null,customerid  int not null, primary key(customerid,salesid),foreign key(customerid)  references  customerinfo(customerid) on delete cascade)engine = innodb;
注意例子中的參數“on delete cascade”。該參數保證當customerinfo表中的一條客戶記錄被刪除的時候,salesinfo表中所有與該客戶相關的記錄也會被自動刪除。如果要在MySQL中使用外鍵,一定要記住在創建表的時候將表的類型定義為事務安全表InnoDB類型。該類型不是MySQL表的默認類型。定義的方法是在CREATE TABLE語句中加上engine=INNODB。如例中所示。

6.對於limit分頁的優化:

第一種使用有索引的列來做order by操作。
第二種limit 2000很慢怎么解決?
1.傳入上一次查詢的id,對limit的優化,不是直接使用limit,而是首先獲取到offset的id(通過這個id來做一遍過濾),然后直接使用limit size來獲取數據。

 ege:select id,name,content from users order by id asc limit 2000,20  一共會掃描2020行

             可是當我們記錄上次最大id    select id,name,content from users where id>2000 order by id asc limit 20   就只用掃描20行(主要還是利用到了id索引,找到第2000個只需要O(log2n的時間復雜度))

2.select id,name,content from users where id>=(select id from users limit 2000,1) limit 20;

3.join子查詢連接

select id,name,content from users a join (select id from users limit 2000,20) b on a.id=b.id; 

      通過反向查詢 order by id des limit (count-2020),20

       如果有where條件需要建立聯合索引

limit分頁優化參考:https://uule.iteye.com/blog/2422189

SELECT * FROM user LIMIT 10000,1.對於它對於優化?

1.select *很有可能返回不需要的列,如果可以指定具體需要哪些列會更好,減少結果集整體大小。
2.limit 10000,1,分不同情況的優化。
對於user表主鍵id是連續的,可以改寫select id,username,age from user where id > 10000 limit 1(利用自增索引)
對於user表主鍵id有斷層的,對於select * from user limit 10000,1是沒有優化空間的。

常見的查詢就是根據索引先獲得主鍵id,再根據這個主鍵id去主鍵索引樹里面去查詢:

Select * From table_name Where id in (Select id From table_name where ( user = xxx )) limit 10000, 10;

select * from table_name where( user = xxx ) limit 10000,10
  • 子查詢只用到了索引列,沒有取實際的數據,所以不涉及到磁盤IO,所以即使是比較大的 offset 查詢速度也不會太差。
  • 利用子查詢的方式,把原來的基於 user 的搜索轉化為基於主鍵(id)的搜索,主查詢因為已經獲得了准確的索引值,所以查詢過程也相對較快。

 

3.在大多數情況下,主鍵id很有可能是斷層的,而且假設實際需求就是select * from user limit 10000,1,加了where條件就不符合需求。那么這種情況下,就只能對表結構和表數據下手了,想辦法將行數據大小減少,例如恰當地將varchar換成int,按照三范式減少數據冗余等,目的是使得每一個數據頁能裝下更多的行數據,即使全表掃描,讀取盡可能少的數據頁。

4.從SELECT * FROM user LIMIT 10000,1來看,就是簡單的獲取user表第10001條數據,看不到有什么實質的查詢意義,而且對於select id,username from user limit 10000,1,這樣在username列上有索引和無索引會使得返回結果集有可能是不一樣的,應該結合業務邏輯來看看有沒有優化空間。

7.使用索引注意項:

負向查詢不能使用索引(盡量避免使用 != 或 not in或 <> 等否定操作符)

select name from user where id not in (1,3,4);

應該修改為:

select name from user where id in (2,5,6);

 

前導模糊查詢不能使用索引

如:

select name from user where name like '%zhangsan'

非前導則可以:

select name from user where name like 'zhangsan%'

建議可以考慮使用 Lucene 等全文索引工具來代替頻繁的模糊查詢。

 

數據區分不明顯的不建議創建索引

如 user 表中的性別字段,可以明顯區分的才建議創建索引,如身份證等字段。

 

字段的默認值不要為 null

這樣會帶來和預期不一致的查詢結果。

 

在字段上進行計算不能命中索引(不要在列上使用函數)

select name from user where FROM_UNIXTIME(create_time) < CURDATE();

應該修改為:

select name from user where create_time < FROM_UNIXTIME(CURDATE());

 

盡量避免使用 or 來連接條件

 

最左前綴問題

如果給 user 表中的 username pwd 字段創建了復合索引那么使用以下SQL 都是可以命中索引:

select username from user where username='zhangsan' and pwd ='axsedf1sd' select username from user where pwd ='axsedf1sd' and username='zhangsan' select username from user where username='zhangsan'

但是使用

select username from user where pwd ='axsedf1sd'

是不能命中索引的。

 

如果明確知道只有一條記錄返回

select name from user where username='zhangsan' limit 1

可以提高效率,可以讓數據庫停止游標移動。

 

不要讓數據庫幫我們做強制類型轉換

select name from user where telno=18722222222

這樣雖然可以查出數據,但是會導致全表掃描。

需要修改為

select name from user where telno='18722222222'

如果需要進行 join 的字段兩表的字段類型要相同

不然也不會命中索引。

 

覆蓋索引的好處

如果一個索引包含所有需要的查詢的字段的值,直接根據索引的查詢結果返回數據,而無需讀表,能夠極大的提高性能。因此,可以定義一個讓索引包含的額外的列,即使這個列對於索引而言是無用的。

 

如果建索引的字段為null我們也是可以用索引的。

 

說一張表里有1千萬條數據,有一個字段status有兩個值(1待審核、2審核通過),然后呢有兩個列表即待審核列表與審核通過的列表,那么如何優化查詢SQL使其列表的查詢速度達到最快?

1.分表,為0的存在一個表里面,為1的存在另外一個表里面。

2.把它全部讀取到Redis的bitmap里面,可以很快的查詢到這些數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM