Sql優化總結


  

1,Sql優化概要:

  Sql優化就是指語句在執行的時候效率不是那么樂觀所以提高Sql優化就是指在數據庫的執行速度的,可通過配置搜索引擎,加索引,分庫分表等等,對Sql的各列進行精確取其范圍值,然后盡量避免全局掃描等等,Sql優化有很多,下面的簡單的總結一下吧(上面都是自己平時講的,沒有那么官方或者說沒有自己更深刻的理解- -。)

1.1,Sql優化的規則

  1. 不要有超過5個以上的表連接(JOIN)
  2. 考慮使用臨時表或表變量存放中間結果。
  3. 少用子查詢
  4. 視圖嵌套不要過深,一般視圖嵌套不要超過2個為宜。

  解釋:
       連接的表越多,其編譯的時間和連接的開銷也越大,性能越不好控制。
    最好是把連接拆開成較小的幾個部分逐個順序執行。
    優先執行那些能夠大量減少結果的連接。
    拆分的好處不僅僅是減少SQL Server優化的時間,更使得SQL語句能夠以你可以預測的方式和順序執行。
    如果一定需要連接很多表才能得到數據,那么很可能意味着設計上的缺陷。
    連接是outer join,非常不好。因為outer join意味着必須對左表或右表查詢所有行。
    如果表很大而沒有相應的where語句,那么outer join很容易導致table scan或index scan。
    要盡量使用inner join避免scan整個表。


 

1.2,Sql查詢緩慢的原因:

    1.數據量過大
    2.表設計不合理
    3.sql語句寫得不好
    4.沒有合理使用索引

  -- 針對SQL語句的優化
     1.查詢語句中不要使用 *
     2.盡量減少子查詢,使用關聯查詢(left join,right join,inner  join)替代
     3.減少使用IN或者NOT IN ,使用exists,not exists或者關聯查詢語句替代
     4.or 的查詢盡量用 union或者union all 代替
    (在確認沒有重復數據或者不用剔除重復數據時,union all會更好)
     5.合理的增加冗余的字段(減少表的聯接查詢)
     6.增加中間表進行優化(這個主要是在統計報表的場景,
    后台開定時任務將數據先統計好,盡量不要在查詢的時候去統計)
     7.建表的時候能使用數字類型的字段就使用數字類型(type,status...),數字類型的字段作為條件查詢比字符串的快
     8.那些可以過濾掉最大數量記錄的條件必須寫在WHERE子句的最末尾
  -- 索引優化
    如果針對sql語句已經沒啥可以優化的,那我們就要考慮加索引了 (下面有對索引的詳細介紹,以及Mysql底層為什么選用b+樹的介紹)。

1.3,Sql語句優化:

  1.3.1,:合理使用like模糊查詢   
     關鍵詞 %姜小魚%,由於姜小魚前面用到了“%”,因此該查詢必然走全表掃描,除非必要,否則不要在關鍵詞前加%
1 select * from student where name like '%姜小魚%' --會造成全表掃描
2 select * from student where name like '姜小魚%' --不會造成全表掃描
   1.3.2,:where子句使用 != 或 <> 操作符優化
      在where子句中使用 != 或 <>操作符,索引將被放棄使用,會進行全表查詢。
1 SELECT id FROM A WHERE ID != 5             --會造成全表掃描
2 SELECT id FROM A WHERE ID>5 OR ID<5        --不會造成全局表描

  1.3.3,:where子句中使用 IS NULL 或 IS NOT NULL 的優化
  在where子句中使用 IS NULL 或 IS NOT NULL 判斷,索引將被放棄使用,會進行全表查詢。

1 SELECT id FROM A WHERE num IS NULL --會造全表掃描
2 SELECT id FROM A WHERE num=0 --優化成num上設置默認值0,確保表中num沒有null值

  1.3.4,:where子句使用or的優化
  很多時候使用union all 或 nuin(必要的時候)的方式替換“or”會得到更好的效果。where子句中使用了or,索引將被放棄使用。

1 SELECT id FROM A WHERE num =10 or num = 20     --索引失效
2 SELECT id FROM A WHERE num = 10 union all SELECT id FROM A WHERE num=20    --優化后

  1.3.5,where子句使用IN 或 NOT IN的優化
  in和not in 也要慎用,否則也會導致全表掃描。
  方案一:between替換in

1 SELECT id FROM A WHERE num in(1,2,3)    --會造成全表掃描
2 SELECT id FROM A WHERE num between 1 and 3    --不會造成全表掃描

  方案二:exist替換in

1 SELECT id FROM A WHERE num in(select num from b ) --會造成全表掃描
2 SELECT num FROM A WHERE num exists(select 1 from B where B.num = A.num)    --不會造成全表掃描

  方案三:left join替換in

1 SELECT id FROM A WHERE num in(select num from B)   --會造成全表掃描
2 SELECT id FROM A LEFT JOIN B ON A.num = B.num    --不會造成全表掃描 

  1.3.6,:where子句中對字段進行表達式操作的優化
  不要在where子句中的“=”左邊進行函數、算數運算或其他表達式運算,否則系統將可能無法正確使用索引。

 1 SELECT id FROM A WHERE num/2 = 100   --會造成索引失效        
 2 SELECT id FROM A WHERE num = 100*2    --優化后
 3  
 4 SELECT id FROM A WHERE substring(name,1,3) = 'abc' --會造成索引失效        
 5 SELECT id FROM A WHERE LIKE 'abc%'  --優化后
 6  
 7 SELECT id FROM A WHERE datediff(day,createdate,'2016-11-30')=0  --會造成索引失效
 8 SELECT id FROM A WHERE createdate>='2016-11-30' and createdate<'2016-12-1'  --不會造成索引失效
 9    
10 SELECT id FROM A WHERE year(addate) <2016    --會造成索引失效
11 SELECT id FROM A where addate<'2016-01-01'  --不會造成索引失效

  1.3.7,:任何地方都不要用 select * from table ,用具體的字段列表替換"*",不要返回用不到的字段 

  1.3.8,:使用“臨時表”暫存中間結果
   采用臨時表暫存中間結果好處:
    (1)避免程序中多次掃描主表,減少程序執行“共享鎖”阻塞“更新鎖”,減少了阻塞,提高了並發性能。
    (2)盡量使用表變量來代替臨時表。如果表變量包含大量數據,請注意索引非常有限(只有主鍵索引)。
    (3)避免頻繁創建和刪除臨時表,以減少系統資源的浪費。
    (4)盡量避免向客戶端返回大數據量,若數據量過大,應考慮相應需求是否合理。


  1.3.9,:limit分頁優化
  當偏移量特別時,limit效率會非常低  

1 SELECT id FROM A LIMIT 1000,10   --超級快
2 SELECT id FROM A LIMIT 90000,10 --特別慢

  優化方法:

1 方法一:select id from A order by id limit 90000,10; 很快,0.04秒就OK。 因為用了id主鍵做索引當然快
2  
3 方法二:select id,title from A where id>=(select id from collect order by id limit 90000,1) limit 10;
4  
5 方法三:select id from A order by id  between 10000000 and 10000010;

     1.4.1,:批量插入優化

1 INSERT into person(name,age) values('A',14) 
2 INSERT into person(name,age) values('B',14) 
3 INSERT into person(name,age) values('C',14) 

  可優化為:

1 INSERT into person(name,age) values('A',14),('B',14),('C',14)

  1.4.2,:利用limit 1 、top 1 取得一行
  有時要查詢一張表時,你知道只需要看一條記錄,你可能去查詢一條特殊的記錄。可以使用limit 1 或者 top 1 來終止數據庫索引繼續掃描整個表或索引。 

SELECT id FROM A LIKE 'abc%'    --優化之前
 
SELECT id FROM A LIKE 'abc%' limit 1    --優化之后

  1.4.3,盡量不要使用 BY RAND()命令
  BY RAND()是隨機顯示結果,這個函數可能會為表中每一個獨立的行執行BY RAND()命令,這個會消耗處理器的處理能力。

1 SELECT * FROM A order by rand() limit 10
2  
3 SELECT * FROM A WHERE id >= ((SELECT MAX(id) FROM A)-(SELECT MIN(id) FROM A)) * RAND() + (SELECT MIN(id) FROM A) LIMIT 10 --優化之后

  1.4.5排序的索引問題 
  Mysql查詢只是用一個索引,因此如果where子句中已經使用了索引的話,那么order by中的列是不會使用索引的。因此數據庫默認排序可以符合要求情況下不要使用排序操作;
盡量不要包含多個列的排序,如果需要最好給這些列創建復合索引。

  1.4.6,盡量用 union all 替換 union
  union和union all的差異主要是前者需要將兩個(或者多個)結果集合並后再進行唯一性過濾操作,這就會涉及到排序,增加大量的cpu運算,加大資源消耗及延遲。所以當我們可以確認不可能出現重復結果集或者不在乎重復結果集的時候,盡量使用union all而不是union

  1.4.7避免類型轉換
  這里所說的“類型轉換”是指where子句中出現column字段的類型和傳入的參數類型不一致的時候發生的類型轉換。人為的上通過轉換函數進行轉換,直接導致mysql無法使用索引。如果非要轉型,應該在傳入參數上進行轉換。
例如utime 是datetime類型,傳入的參數是“2016-07-23”,在比較大小時通常是 date(utime)>"2016-07-23",可以優化為utime>"2016-07-23 00:00:00"

  1.4.8盡可能使用更小的字段         
  MySQL從磁盤讀取數據后是存儲到內存中的,然后使用cpu周期和磁盤I/O讀取它,這意味着越小的數據類型占用的空間越小,從磁盤讀或打包到內存的效率都更好,但也不要太過執着減小數據類型,要是以后應用程序發生什么變化就沒有空間了。
修改表將需要重構,間接地可能引起代碼的改變,這是很頭疼的問題,因此需要找到一個平衡點。

  1.4.9,Inner join 和 left join、right join、子查詢
  第一:inner join內連接也叫等值連接是,left/rightjoin是外連接。 

1 SELECT A.id,A.name,B.id,B.name FROM A LEFT JOIN B ON A.id =B.id;
2  
3 SELECT A.id,A.name,B.id,B.name FROM A RIGHT JOIN ON B A.id= B.id;
4  
5 SELECT A.id,A.name,B.id,B.name FROM A INNER JOIN ON A.id =B.id;

  經過來之多方面的證實inner join性能比較快,因為inner join是等值連接,或許返回的行數比較少。但是我們要記得有些語句隱形的用到了等值連接,如:

 1 SELECT A.id,A.name,B.id,B.name FROM A,B WHERE A.id = B.id;  

  推薦:能用inner join連接盡量使用inner join連接
  第二:子查詢的性能又比外連接性能慢,盡量用外連接來替換子查詢。

1 Select* from A where exists (select * from B where id>=3000 and A.uuid=B.uuid);

  A表的數據為十萬級表,B表為百萬級表,在本機執行差不多用2秒左右,我們可以通過explain可以查看到子查詢是一個相關子查詢(DEPENDENCE SUBQUERY);Mysql是先對外表A執行全表查詢,然后根據uuid逐次執行子查詢,如果外層表是一個很大的表,我們可以想象查詢性能會表現比這個更加糟糕。
  一種簡單的優化就是用innerjoin的方法來代替子查詢,查詢語句改為:

1 Select* from A inner join B ON A.uuid=B.uuid using(uuid) where b.uuid>=3000;  這個語句執行測試不到一秒;

  第三:使用JOIN時候,應該用小的結果驅動打的結果
  (left join 左邊表結果盡量小,如果有條件應該放到左邊先處理,right join同理反向),同時盡量把牽涉到多表聯合的查詢拆分多個query (多個表查詢效率低,容易鎖表和阻塞)。如:

1 Select * from A left join B A.id=B.ref_id where  A.id>10;可以優化為:select * from (select * from A wehre id >10) T1 left join B on T1.id=B.ref_id;

  1.5,exist 代替 in 

1 SELECT * from A WHERE idin (SELECT id from B)
2  
3 SELECT * from A WHERE id EXISTS(SELECT 1 from A.id= B.id)

  in 是在內存中遍歷比較
  exist 需要查詢數據庫,所以當B的數據量比較大時,exists效率優於in.
  in()只執行一次,把B表中的所有id字段緩存起來,之后檢查A表的id是否與B表中的id相等,如果id相等則將A表的記錄加入到結果集中,直到遍歷完A表的所有記錄。
  in()適合B表比A表數據小的情況,exists()適合B表比A表數據大的情況。


 

  還有很多可能我沒收集到等以后再想到或者看到的話就再添加上去。 

2,索引的概要:

    索引對查詢的速度有着至關重要的影響,理解索引也是進行數據庫性能調優的起點。考慮如下情況,假設數據庫中一個表有10^6條記錄,DBMS的頁面大小為4K,並存儲100條記錄。如果沒有索引,查詢將對整個表進行掃描,最壞的情況下,如果所有數據頁都不在內存,需要讀取10^4個頁面,如果這10^4個頁面在磁盤上隨機分布,需要進行10^4次I/O,假設磁盤每次I/O時間為10ms(忽略數據傳輸時間),則總共需要100s(但實際上要好很多很多)。如果對之建立B-Tree索引,則只需要進行log100(10^6)=3次頁面讀取,最壞情況下耗時30ms。這就是索引帶來的效果,很多時候,當你的應用程序進行SQL查詢速度很慢時,應該想想是否可以建索引。

2.1,索引的分類:

注意:

  索引是在存儲引擎中實現的,也就是說不同的存儲引擎,會使用不同的索引。MyISAM和InnoDB存儲引擎:只支持BTREE索引,也就是說默認使用BTREE,不能夠更換。  MEMORY/HEAP存儲引擎:支持HASH和BTREE索引。

分類:

索引我們分為四類來講單列索引(普通索引,唯一索引,主鍵索引)、組合索引、全文索引、空間索引、
  2.2.1、單列索引:

一個索引只包含單個列,但一個表中可以有多個單列索引。 這里不要搞混淆了。
  2.2.2、普通索引:MySQL中基本索引類型,沒有什么限制,允許在定義索引的列中插入重復值和空值,純粹為了查詢數據更快一點。
  2.2.3、唯一索引:索引列中的值必須是唯一的,但是允許為空值,
  2.2.4、主鍵索引:是一種特殊的唯一索引,不允許有空值。(主鍵約束,就是一個主鍵索引)
  2.3、組合索引:在表中的多個字段組合上創建的索引,只有在查詢條件中使用了這些字段的左邊字段時,索引才會被使用,使用組合索引時遵循最左前綴集合。例如,這里由id、name和age3個字段構成的索引,索引行中就按id/name/age的順序存放,索引可以索引下面字段組合(id,name,age)、(id,name)或者(id)。如果要查詢的字段不構成索引最左面的前綴,那么就不會是用索引,比如,age或者(name,age)組合就不會使用索引查詢
  2.4、全文索引:全文索引,只有在MyISAM引擎上才能使用,只能在CHAR,VARCHAR,TEXT類型字段上使用全文索引,介紹了要求,說說什么是全文索引,就是在一堆文字中,通過其中的某個關鍵字等,就能找到該字段所屬的記錄行,比如有"你是個大煞筆,二貨 ..." 通過大煞筆,可能就可以找到該條記錄。這里說的是可能,因為全文索引的使用涉及了很多細節,我們只需要知道這個大概意思。
  2.5、空間索引:空間索引是對空間數據類型的字段建立的索引,MySQL中的空間數據類型有四種,GEOMETRY、POINT、LINESTRING、POLYGON。在創建空間索引時,使用SPATIAL關鍵字。要求,引擎為MyISAM,創建空間索引的列,必須將其聲明為NOT NULL。可能跟游戲開發有關。

2.6,Mysql索引為什么使用B+樹實現:

 

從上圖你能看到,一個內結點x若含有n[x]個關鍵字,那么x將含有n[x]+1個子女

為什么說B+-tree比B 樹更適合實際應用中操作系統的文件索引和數據庫索引?

2.6.1)B+樹的磁盤讀寫代價更低

    B+樹的內部結點並沒有指向關鍵字具體信息的指針。因此其內部結點相對於B樹更小。如果把所有同一內部結點的關鍵字存放在同一盤塊中,那么盤塊所能容納的關鍵字數量也越多。一次性讀入內存中的需要查找的關鍵字也就越多。相對來說IO讀寫次數也就降低了。

2.6.2) B+-tree的查詢效率更加穩定

  由於非終結點並不是最終指向文件內容的結點,而只是葉子結點中關鍵字的索引。所以任何關鍵字的查找必須走一條從根結點到葉子結點的路。所有關鍵字查詢的路徑長度相同,導致每一個數據的查詢效率相當。
MySQL索引實現
  MyISAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。因此,MyISAM中索引檢索的算法為首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,則取出其data域的值,然后以data域的值為地址,讀取相應數據記錄。而在InnoDB中,表數據文件本身就是按B+Tree組織的一個索引結構,這棵樹的葉結點data域保存了完整的數據記錄。

2.7,有可能造成索引失效的幾種情況

    索引以最左前綴原則使用的~
   1、使用like關鍵字模糊查詢時,% 放在前面索引不起作用,只有“%”不在第一個位置,索引才會生效(like '%文'--索引不起作用)
   2、使用聯合索引時,只有查詢條件中使用了這些字段中的第一個字段,索引才會生效
   3、使用OR關鍵字的查詢,查詢語句的查詢條件中只有OR關鍵字,且OR前后的兩個條件中的列都是索引時,索引才會生效,否則索引不生效。
   4、盡量避免在where子句中使用!=或<>操作符,否則引擎將放棄使用索引而進行全表掃描。
   5、對查詢進行優化,應盡量避免全表掃描,首先應考慮在where以及order by涉及的列上建立索引。
   6、應盡量避免在 where 子句中對字段進行表達式操作,這將導致引擎放棄使用索引而進行全表掃描。如:
      select id from t where num/2=100 
      應改為: 
      select id from t where num=100*2 
   7、盡量避免在where子句中對字段進行函數操作,將導致引擎放棄使用索引而進行全表掃描。
   8、不要在 where 子句中的“=”左邊進行函數、算術運算或其他表達式運算,否則系統將可能無法正確使用索引。
   9、並不是所有的索引對查詢都有效,sql是根據表中的數據來進行查詢優化的,當索引列有大量數據重復時,sql查詢不會去利用索引,如一表中有字段
    sex,male,female幾乎個一半,那么即使在sex上建立了索引也對查詢效率起不了作用。
   10、索引並不是越多越好,索引固然可以提高相應的 select 的效率,但同時也降低了 insert 及 update 的效率,
     因為 insert 或 update 時有可能會重建索引,所以怎樣建索引需要慎重考慮,視具體情況而定。一個表的索引數最好不要超過6個,
     若太多則應考慮一些不常使用到的列上建的索引是否有 必要。
   11、盡量使用數字型字段,若只含數值信息的字段盡量不要設計為字符型,這會降低查詢和連接的性能,並會增加存儲開銷。
     這是因為引擎在處理查詢和連接時會 逐個比較字符串中每一個字符,而對於數字型而言只需要比較一次就夠了。
   12、mysql查詢只使用一個索引,因此如果where子句中已經使用了索引的話,那么order by中的列是不會使用索引的。
     因此數據庫默認排序可以符合要求的情況下不要使用排序操作,盡量不要包含多個列的排序,如果需要最好給這些列建復合索引。
   13、order by 索引 ,不起作用的問題(除了主鍵索引之外):
      1、 如果select 只查詢索引字段,order by 索引字段會用到索引,要不然就是全表排列;
      2、如果有where 條件,比如where vtype=1 order by vtype asc . 這樣order by 也會用到索引!


免責聲明!

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



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