成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

聊聊 SQL 優化的 15 個小技巧

運維 數據庫運維
如果某天你負責的某個線上接口,出現了性能問題,需要做優化。那么你首先想到的很有可能是優化sql語句,因為它的改造成本相對于代碼來說也要小得多。

[[434423]]

前言

sql優化是一個大家都比較關注的熱門話題,無論你在面試,還是工作中,都很有可能會遇到。

如果某天你負責的某個線上接口,出現了性能問題,需要做優化。那么你首先想到的很有可能是優化sql語句,因為它的改造成本相對于代碼來說也要小得多。

那么,如何優化sql語句呢?

這篇文章從15個方面,分享了sql優化的一些小技巧,希望對你有所幫助。

1.避免使用select *

很多時候,我們寫sql語句時,為了方便,喜歡直接使用select *,一次性查出表中所有列的數據。

反例:

  1. select * from user where id=1; 

在實際業務場景中,可能我們真正需要使用的只有其中一兩列。查了很多數據,但是不用,白白浪費了數據庫資源,比如:內存或者cpu。

此外,多查出來的數據,通過網絡IO傳輸的過程中,也會增加數據傳輸的時間。

還有一個最重要的問題是:select *不會走覆蓋索引,會出現大量的回表操作,而從導致查詢sql的性能很低。

那么,如何優化呢?

正例:

  1. select name,age from user where id=1; 

sql語句查詢時,只查需要用到的列,多余的列根本無需查出來。

2.用union all代替union

我們都知道sql語句使用union關鍵字后,可以獲取排重后的數據。

而如果使用union all關鍵字,可以獲取所有數據,包含重復的數據。

反例:

  1. (select * from user where id=1)  
  2. union  
  3. (select * from user where id=2); 

排重的過程需要遍歷、排序和比較,它更耗時,更消耗cpu資源。

所以如果能用union all的時候,盡量不用union。

正例:

  1. (select * from user where id=1)  
  2. union all 
  3. (select * from user where id=2); 

除非是有些特殊的場景,比如union all之后,結果集中出現了重復數據,而業務場景中是不允許產生重復數據的,這時可以使用union。

3.小表驅動大表

小表驅動大表,也就是說用小表的數據集驅動大表的數據集。

假如有order和user兩張表,其中order表有10000條數據,而user表有100條數據。

這時如果想查一下,所有有效的用戶下過的訂單列表。

可以使用in關鍵字實現:

  1. select * from order 
  2.  
  3. where user_id in (select id from user where status=1) 

也可以使用exists關鍵字實現:

  1. select * from order 
  2.  
  3. where exists (select 1 from user where order.user_id = user.id and status=1) 

前面提到的這種業務場景,使用in關鍵字去實現業務需求,更加合適。

為什么呢?

因為如果sql語句中包含了in關鍵字,則它會優先執行in里面的子查詢語句,然后再執行in外面的語句。如果in里面的數據量很少,作為條件查詢速度更快。

而如果sql語句中包含了exists關鍵字,它優先執行exists左邊的語句(即主查詢語句)。然后把它作為條件,去跟右邊的語句匹配。如果匹配上,則可以查詢出數據。如果匹配不上,數據就被過濾掉了。

這個需求中,order表有10000條數據,而user表有100條數據。order表是大表,user表是小表。如果order表在左邊,則用in關鍵字性能更好。

總結一下:

  • in 適用于左邊大表,右邊小表。
  • exists 適用于左邊小表,右邊大表。

不管是用in,還是exists關鍵字,其核心思想都是用小表驅動大表。

4.批量操作

如果你有一批數據經過業務處理之后,需要插入數據,該怎么辦?

反例:

  1. for(Order order: list){ 
  2.  
  3. orderMapper.insert(order): 
  4.  

在循環中逐條插入數據。

  1. insert into order(id,code,user_id) 
  2.  
  3. values(123,'001',100); 

該操作需要多次請求數據庫,才能完成這批數據的插入。

但眾所周知,我們在代碼中,每次遠程請求數據庫,是會消耗一定性能的。而如果我們的代碼需要請求多次數據庫,才能完成本次業務功能,勢必會消耗更多的性能。

那么如何優化呢?

正例:

  1. orderMapper.insertBatch(list): 

提供一個批量插入數據的方法。

  1. insert into order(id,code,user_id)  
  2. values(123,'001',100),(124,'002',100),(125,'003',101); 

這樣只需要遠程請求一次數據庫,sql性能會得到提升,數據量越多,提升越大。

但需要注意的是,不建議一次批量操作太多的數據,如果數據太多數據庫響應也會很慢。批量操作需要把握一個度,建議每批數據盡量控制在500以內。如果數據多于500,則分多批次處理。

5.多用limit

有時候,我們需要查詢某些數據中的第一條,比如:查詢某個用戶下的第一個訂單,想看看他第一次的首單時間。

反例:

  1. select id, create_date 
  2.  
  3. from order 
  4.  
  5. where user_id=123 
  6.  
  7. order by create_date asc

根據用戶id查詢訂單,按下單時間排序,先查出該用戶所有的訂單數據,得到一個訂單集合。然后在代碼中,獲取第一個元素的數據,即首單的數據,就能獲取首單時間。

  1. List<Order> list = orderMapper.getOrderList(); 
  2. Order order = list.get(0); 

雖說這種做法在功能上沒有問題,但它的效率非常不高,需要先查詢出所有的數據,有點浪費資源。

那么,如何優化呢?

正例:

  1. select id, create_date 
  2.  
  3. from order 
  4.  
  5. where user_id=123 
  6.  
  7. order by create_date asc 
  8.  
  9. limit 1; 

使用limit 1,只返回該用戶下單時間最小的那一條數據即可。

此外,在刪除或者修改數據時,為了防止誤操作,導致刪除或修改了不相干的數據,也可以在sql語句最后加上limit。

例如:

  1. update order set status=0,edit_time=now(3) 
  2.  
  3. where id>=100 and id<200 limit 100; 

這樣即使誤操作,比如把id搞錯了,也不會對太多的數據造成影響。

6.in中值太多

對于批量查詢接口,我們通常會使用in關鍵字過濾出數據。比如:想通過指定的一些id,批量查詢出用戶信息。

sql語句如下:

  1. select id,name from category 
  2. where id in (1,2,3...100000000); 

如果我們不做任何限制,該查詢語句一次性可能會查詢出非常多的數據,很容易導致接口超時。

這時該怎么辦呢?

  1. select id,name from category 
  2. where id in (1,2,3...100) 
  3. limit 500; 

可以在sql中對數據用limit做限制。

不過我們更多的是要在業務代碼中加限制,偽代碼如下:

  1. public List<Category> getCategory(List<Long> ids) { 
  2.    if(CollectionUtils.isEmpty(ids)) { 
  3.       return null
  4.    } 
  5.    if(ids.size() > 500) { 
  6.       throw new BusinessException("一次最多允許查詢500條記錄"
  7.    } 
  8.    return mapper.getCategoryList(ids); 

還有一個方案就是:如果ids超過500條記錄,可以分批用多線程去查詢數據。每批只查500條記錄,最后把查詢到的數據匯總到一起返回。

不過這只是一個臨時方案,不適合于ids實在太多的場景。因為ids太多,即使能快速查出數據,但如果返回的數據量太大了,網絡傳輸也是非常消耗性能的,接口性能始終好不到哪里去。

7.增量查詢

有時候,我們需要通過遠程接口查詢數據,然后同步到另外一個數據庫。

反例:

  1. select * from user

如果直接獲取所有的數據,然后同步過去。這樣雖說非常方便,但是帶來了一個非常大的問題,就是如果數據很多的話,查詢性能會非常差。

這時該怎么辦呢?

正例:

  1. select * from user 
  2.  
  3. where id>#{lastId} and create_time >= #{lastCreateTime} 
  4.  
  5. limit 100; 

按id和時間升序,每次只同步一批數據,這一批數據只有100條記錄。每次同步完成之后,保存這100條數據中最大的id和時間,給同步下一批數據的時候用。

通過這種增量查詢的方式,能夠提升單次查詢的效率。

8.高效的分頁

有時候,列表頁在查詢數據時,為了避免一次性返回過多的數據影響接口性能,我們一般會對查詢接口做分頁處理。

在mysql中分頁一般用的limit關鍵字:

  1. select id,name,age 
  2.  
  3. from user limit 10,20; 

如果表中數據量少,用limit關鍵字做分頁,沒啥問題。但如果表中數據量很多,用它就會出現性能問題。

比如現在分頁參數變成了:

  1. select id,name,age 
  2.  
  3. from user limit 1000000,20; 

mysql會查到1000020條數據,然后丟棄前面的1000000條,只查后面的20條數據,這個是非常浪費資源的。

那么,這種海量數據該怎么分頁呢?

優化sql:

  1. select id,name,age 
  2.  
  3. from user where id > 1000000 limit 20; 

先找到上次分頁最大的id,然后利用id上的索引查詢。不過該方案,要求id是連續的,并且有序的。

還能使用between優化分頁。

  1. select id,name,age 
  2.  
  3. from user where id between 1000000 and 1000020; 

需要注意的是between要在唯一索引上分頁,不然會出現每頁大小不一致的問題。

9.用連接查詢代替子查詢

mysql中如果需要從兩張以上的表中查詢出數據的話,一般有兩種實現方式:子查詢 和 連接查詢。

子查詢的例子如下:

  1. select * from order 
  2.  
  3. where user_id in (select id from user where status=1) 

子查詢語句可以通過in關鍵字實現,一個查詢語句的條件落在另一個select語句的查詢結果中。程序先運行在嵌套在最內層的語句,再運行外層的語句。

子查詢語句的優點是簡單,結構化,如果涉及的表數量不多的話。

但缺點是mysql執行子查詢時,需要創建臨時表,查詢完畢后,需要再刪除這些臨時表,有一些額外的性能消耗。

這時可以改成連接查詢。具體例子如下:

  1. select o.* from order o 
  2. inner join user u on o.user_id = u.id 
  3. where u.status=1 

10.join的表不宜過多

根據阿里巴巴開發者手冊的規定,join表的數量不應該超過3個。

反例:

  1. select a.name,b.name.c.name,d.name 
  2. from a  
  3. inner join b on a.id = b.a_id 
  4. inner join c on c.b_id = b.id 
  5. inner join d on d.c_id = c.id 
  6. inner join e on e.d_id = d.id 
  7. inner join f on f.e_id = e.id 
  8. inner join g on g.f_id = f.id 

如果join太多,mysql在選擇索引的時候會非常復雜,很容易選錯索引。

并且如果沒有命中中,nested loop join 就是分別從兩個表讀一行數據進行兩兩對比,復雜度是 n^2。

所以我們應該盡量控制join表的數量。

正例:

  1. select a.name,b.name.c.name,a.d_name 
  2.  
  3. from a 
  4.  
  5. inner join b on a.id = b.a_id 
  6.  
  7. inner join c on c.b_id = b.id 

如果實現業務場景中需要查詢出另外幾張表中的數據,可以在a、b、c表中冗余專門的字段,比如:在表a中冗余d_name字段,保存需要查詢出的數據。

不過我之前也見過有些ERP系統,并發量不大,但業務比較復雜,需要join十幾張表才能查詢出數據。

所以join表的數量要根據系統的實際情況決定,不能一概而論,盡量越少越好。

11.join時要注意

我們在涉及到多張表聯合查詢的時候,一般會使用join關鍵字。

而join使用最多的是left join和inner join。

  • left join:求兩個表的交集外加左表剩下的數據。
  • inner join:求兩個表交集的數據。

使用inner join的示例如下:

  1. select o.id,o.code,u.name 
  2.  
  3. from order o 
  4.  
  5. inner join user u on o.user_id = u.id 
  6.  
  7. where u.status=1; 

如果兩張表使用inner join關聯,mysql會自動選擇兩張表中的小表,去驅動大表,所以性能上不會有太大的問題。

使用left join的示例如下:

  1. select o.id,o.code,u.name 
  2.  
  3. from order o 
  4.  
  5. left join user u on o.user_id = u.id 
  6.  
  7. where u.status=1; 

如果兩張表使用left join關聯,mysql會默認用left join關鍵字左邊的表,去驅動它右邊的表。如果左邊的表數據很多時,就會出現性能問題。

要特別注意的是在用left join關聯查詢時,左邊要用小表,右邊可以用大表。如果能用inner join的地方,盡量少用left join。

12.控制索引的數量

眾所周知,索引能夠顯著的提升查詢sql的性能,但索引數量并非越多越好。

因為表中新增數據時,需要同時為它創建索引,而索引是需要額外的存儲空間的,而且還會有一定的性能消耗。

阿里巴巴的開發者手冊中規定,單表的索引數量應該盡量控制在5個以內,并且單個索引中的字段數不超過5個。

mysql使用的B+樹的結構來保存索引的,在insert、update和delete操作時,需要更新B+樹索引。如果索引過多,會消耗很多額外的性能。

那么,問題來了,如果表中的索引太多,超過了5個該怎么辦?

這個問題要辯證的看,如果你的系統并發量不高,表中的數據量也不多,其實超過5個也可以,只要不要超過太多就行。

但對于一些高并發的系統,請務必遵守單表索引數量不要超過5的限制。

那么,高并發系統如何優化索引數量?

能夠建聯合索引,就別建單個索引,可以刪除無用的單個索引。

將部分查詢功能遷移到其他類型的數據庫中,比如:Elastic Seach、HBase等,在業務表中只需要建幾個關鍵索引即可。

13.選擇合理的字段類型

char表示固定字符串類型,該類型的字段存儲空間的固定的,會浪費存儲空間。

  1. alter table order 
  2.  
  3. add column code char(20) NOT NULL

varchar表示變長字符串類型,該類型的字段存儲空間會根據實際數據的長度調整,不會浪費存儲空間。

  1. alter table order 
  2.  
  3. add column code varchar(20) NOT NULL

如果是長度固定的字段,比如用戶手機號,一般都是11位的,可以定義成char類型,長度是11字節。

但如果是企業名稱字段,假如定義成char類型,就有問題了。

如果長度定義得太長,比如定義成了200字節,而實際企業長度只有50字節,則會浪費150字節的存儲空間。

如果長度定義得太短,比如定義成了50字節,但實際企業名稱有100字節,就會存儲不下,而拋出異常。

所以建議將企業名稱改成varchar類型,變長字段存儲空間小,可以節省存儲空間,而且對于查詢來說,在一個相對較小的字段內搜索效率顯然要高些。

我們在選擇字段類型時,應該遵循這樣的原則:

  • 能用數字類型,就不用字符串,因為字符的處理往往比數字要慢。
  • 盡可能使用小的類型,比如:用bit存布爾值,用tinyint存枚舉值等。
  • 長度固定的字符串字段,用char類型。
  • 長度可變的字符串字段,用varchar類型。
  • 金額字段用decimal,避免精度丟失問題。

還有很多原則,這里就不一一列舉了。

14.提升group by的效率

我們有很多業務場景需要使用group by關鍵字,它主要的功能是去重和分組。

通常它會跟having一起配合使用,表示分組后再根據一定的條件過濾數據。

反例:

  1. select user_id,user_name from order 
  2.  
  3. group by user_id 
  4.  
  5. having user_id <= 200; 

這種寫法性能不好,它先把所有的訂單根據用戶id分組之后,再去過濾用戶id大于等于200的用戶。

分組是一個相對耗時的操作,為什么我們不先縮小數據的范圍之后,再分組呢?

正例:

  1. select user_id,user_name from order 
  2.  
  3. where user_id <= 200 
  4.  
  5. group by user_id 

使用where條件在分組前,就把多余的數據過濾掉了,這樣分組時效率就會更高一些。

其實這是一種思路,不僅限于group by的優化。我們的sql語句在做一些耗時的操作之前,應盡可能縮小數據范圍,這樣能提升sql整體的性能。

15.索引優化

sql優化當中,有一個非常重要的內容就是:索引優化。

很多時候sql語句,走了索引,和沒有走索引,執行效率差別很大。所以索引優化被作為sql優化的首選。

索引優化的第一步是:檢查sql語句有沒有走索引。

那么,如何查看sql走了索引沒?

可以使用explain命令,查看mysql的執行計劃。

例如:

  1. explain select * from `orderwhere code='002'

結果:

通過這幾列可以判斷索引使用情況,執行計劃包含列的含義如下圖所示:

如果你想進一步了解explain的詳細用法,可以看看我的另一篇文章《explain | 索引優化的這把絕世好劍,你真的會用嗎?》

說實話,sql語句沒有走索引,排除沒有建索引之外,最大的可能性是索引失效了。

下面說說索引失效的常見原因:

如果不是上面的這些原因,則需要再進一步排查一下其他原因。

此外,你有沒有遇到過這樣一種情況:明明是同一條sql,只有入參不同而已。有的時候走的索引a,有的時候卻走的索引b?

沒錯,有時候mysql會選錯索引。

必要時可以使用force index來強制查詢sql走某個索引。

至于為什么mysql會選錯索引,后面有專門的文章介紹的,這里先留點懸念。

 

 

責任編輯:武曉燕 來源: 蘇三說技術
相關推薦

2021-11-18 08:20:22

接口索引SQL

2022-08-26 17:48:34

數據庫建表數據庫

2022-07-20 08:21:00

Java代碼優化

2024-10-28 08:34:06

2009-06-18 11:12:42

Hibernate S優化

2024-01-22 13:16:00

接口性能優化本地緩存

2022-10-09 13:36:44

接口性能優化

2022-02-21 13:27:11

接口性能優化索引命令

2022-04-02 09:56:44

pipPython

2023-09-25 13:15:50

SQL數據庫

2020-11-11 08:22:40

前端開發JavaScript

2018-02-23 13:55:16

ASP.NET性能優化技巧

2022-11-24 10:34:05

CSS前端

2021-05-07 16:02:54

Python代碼優化

2021-06-16 10:50:16

Python代碼優化

2024-06-21 08:21:44

2022-03-10 08:01:06

CSS技巧選擇器

2021-02-03 10:46:31

SQL數據庫技巧

2011-05-10 17:06:05

SEO

2023-07-19 15:16:33

遠程辦公技巧
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 黄色一级大片在线免费看产 | 一区二区三区免费 | 国产免费一区二区 | 日本三级电影在线看 | 国产色网站 | 欧美一区二区久久 | 久国久产久精永久网页 | 国产精品久久精品 | 91婷婷韩国欧美一区二区 | 成人精品在线观看 | 亚洲国产精品久久 | 久久久久一区二区 | 国产成人精品免费视频大全最热 | 蜜桃视频在线观看www社区 | 久久精品国产一区二区电影 | 欧美国产日韩在线观看 | 操皮视频 | 亚洲国产二区 | 在线视频国产一区 | 亚洲一区二区av | 欧美一级在线免费观看 | 日韩视频观看 | 精品在线一区 | 久久久久九九九女人毛片 | 中文字幕在线观看第一页 | 91精品国产综合久久婷婷香蕉 | 成人福利网站 | 综合久 | 国内自拍真实伦在线观看 | 日韩激情视频一区 | 视频一区二区三区在线观看 | 国产精品1区2区 | 成人精品一区二区三区 | 亚洲国产精品久久久久秋霞不卡 | 91视频免费黄 | 男女羞羞视频网站 | 日本三级线观看 视频 | 久久精品国产一区二区 | av黄在线观看 | 九九热精品免费 | 久久久久久久91 |