天天看點

一次接口性能優化實踐:接口性能從11s降到了100ms提升了百倍

作者:架構師成長曆程

前言

最近對外接口偶現504逾時問題,原因是代碼執行時間過長,超過nginx配置的15秒,然後真槍實彈搞了一次接口性能優化。在這裡結合優化過程,總結了接口優化的八個要點,希望對大家有幫助呀~

  • 資料量比較大,批量操作資料入庫
  • 耗時操作考慮異步處理
  • 恰當使用緩存
  • 優化程式邏輯、代碼
  • SQL優化
  • 壓縮傳輸内容
  • 考慮使用檔案/MQ等其他方式暫存,異步再落地DB
  • 跟産品讨論需求最恰當,最舒服的實作方式

嘻嘻,先看一下我們對外轉賬接口的大概流程吧

一次接口性能優化實踐:接口性能從11s降到了100ms提升了百倍

1.資料量比較大,批量操作資料入庫

優化前:

//for循環單筆入庫
for(TransDetail detail:list){
  insert(detail);  
}
複制代碼           

優化後:

// 批量入庫,mybatis demo實作
<insert id="insertBatch" parameterType="java.util.List">
insert into trans_detail( id,amount,payer,payee) values
 <foreach collection="list" item="item" index="index" separator=",">(
    #{item.id},	#{item.amount},
    #{item.payer},#{item.payee}
  )
</foreach>
</insert>
複制代碼           

性能對比:

機關(ms) for循環單筆入庫 批量入庫
500條 1432 1153
1000條 1876 1425

解析

  • 批量插入性能更好,更加省時間,為什麼呢?
打個比喻:假如你需要搬一萬塊磚到樓頂,你有一個電梯,電梯一次可以放适量的磚(最多放500),
你可以選擇一次運送一塊磚,也可以一次運送500,你覺得哪種方式更友善,時間消耗更少?
複制代碼           

2.耗時操作考慮異步處理

耗時操作,考慮用異步處理,這樣可以降低接口耗時。本次轉賬接口優化,比對聯行号的操作耗時有點長,是以優化過程把它移到異步處理啦,如下:

優化前:

一次接口性能優化實踐:接口性能從11s降到了100ms提升了百倍

優化後

比對聯行号的操作異步處理

一次接口性能優化實踐:接口性能從11s降到了100ms提升了百倍

性能對比:

假設一個聯行号比對6ms

同步 異步
500條 3000ms ~
1000條 6000ms ~

解析:

  • 因為聯行号比對比較耗時,放在異步處理的話,同步聯機傳回可以省掉這部分時間,大大提升接口性能,并且不會影響到轉賬主流程功能。
  • 除了這個例子,平時我們類似功能,如使用者注冊成功後,短信郵件通知,也是可以異步處理的,這個優化建議香饽饽的~
  • 是以,太耗時的操作,在不影響主流程功能的情況下,可以考慮開子線程異步處理的啦。

3.恰當使用緩存

在适當的業務場景,恰當地使用緩存,是可以大大提高接口性能的。這裡的緩存包括:Redis,JVM本地緩存,memcached,或者Map等。

這次轉賬接口,使用到緩存啦,舉個簡單例子吧~

優化前

以下是輸入使用者賬号,比對聯行号的流程圖

一次接口性能優化實踐:接口性能從11s降到了100ms提升了百倍

優化後:

恰當使用緩存,代替查詢DB表,流程圖如下:

一次接口性能優化實踐:接口性能從11s降到了100ms提升了百倍

解析:

  • 把熱點資料放到緩存,不用每次查詢都去DB拉取,節省了這部分查SQL的耗時,美滋滋呀~
  • 當然,不是什麼資料都适合放到緩存的哦,通路比較頻繁的熱點資料才考慮緩存起來呢~

4. 優化程式邏輯、代碼

優化程式邏輯、程式代碼,是可以節省耗時的。

我這裡就本次的轉賬接口優化,舉個例子吧~

優化前:

優化前,聯行号查詢了兩次(檢驗參數一次,插入DB前查詢一次),如下僞代碼:

punlic void process(Req req){
  //檢驗參數,包括聯行号(前端傳來的payeeBankNo可以為空,但是如果後端沒比對到,會抛異常)
   checkTransParams(Req req);
   //Save DB
  saveTransDetail(req); 
}

void checkTransParams(Req req){
    //check Amount,and so on.
    checkAmount(req.getamount);
    //check payeebankNo
    if(Utils.isEmpty(req.getPayeeBankNo())){
        String payeebankNo = getPayeebankNo(req.getPayeeAccountNo);
        if(Utils.isEmpty(payeebankNo){
            throws Exception();
        }
    }
}

int saveTransDetail(req){
    String payeebankNo = getPayeebankNo(req.getPayeeAccountNo);
    req.setPayeeBankNo(payeebankNo);
    insert(req);
    ...
}

複制代碼           

優化後:

優化後,隻在校驗參數的時候插叙一次,然後設定到對象裡面~ 入庫前就不用再查啦,僞代碼如下:

void checkTransParams(Req req){
    //check Amount,and so on.
    checkAmount(req.getamount);
    //check payeebankNo
    if(Utils.isEmpty(req.getPayeeBankNo())){
        String payeebankNo = getPayeebankNo(req.getPayeeAccountNo);
        if(Utils.isEmpty(payeebankNo){
            throws Exception();
        }
    }
    //查詢到有聯行号,直接設定進去啦,這樣等下入庫不用再插入多一次
    req.setPayeeBankNo(payeebankNo);
}

int saveTransDetail(req){
    insert(req);
    ...
}
複制代碼           

解析:

  • 對于優化程式邏輯、代碼,是可以降低接口耗時的。以上demo隻是一個很簡單的例子,就是優化前payeeBankNo查詢了兩次,但是其實隻查一次就可以了。很多時候,我們都知道這個點,但就是到寫代碼的時候,又忘記了呀~是以,寫代碼的時候,留點心吧,優化你的程式邏輯、代碼哦。
  • 除了以上demo這點,還有其它的點,如優化if複雜的邏輯條件,考慮是否可以調整順序,或者for循環,是否重複執行個體化對象等等,這些适當優化,都是可以讓你的代碼跑得更快的。

之前我這篇文章,也提了幾個優化點噢,有興趣的朋友可以看一下哈~

寫代碼有這些想法,同僚才不會認為你是複制粘貼程式員

5. 優化你的SQL

很多時候,你的接口性能瓶頸就在SQL這裡,慢查詢需要我們重點關注的點呢。

我們可以通過這些方式優化我們的SQL:

  • 加索引
  • 避免傳回不必要的資料
  • 優化sql結構
  • 分庫分表
  • 讀寫分離

有興趣的朋友可以看一下我這篇文章呢,很詳細的SQL優化點:

後端程式員必備:書寫高品質SQL的30條建議

6.壓縮傳輸内容

壓縮傳輸内容,檔案變得更小,是以傳輸會更快啦。10M帶寬,傳輸10k的封包,一般比傳輸1M的會快呀;打個比喻,一匹千裡馬,它馱着一百斤的貨跑得快,還是馱着10斤的貨物跑得快呢?

解析:

  • 如果你的接口性能不好,然後傳輸封包比較大的話,這時候是可以考慮壓縮檔案内容傳輸的,最後優化效果可能很不錯哦~

7. 考慮使用檔案/MQ等其他方式暫存資料,異步再落地DB

如果資料太大,落地資料庫實在是慢的話,可以考慮先用檔案的方式儲存,或者考慮MQ,先落地,再異步儲存到資料庫~

本次轉賬接口,如果是并發開啟,10個并發度,每個批次1000筆資料,資料庫插入會特别耗時,大概10秒左右,這個跟我們公司的資料庫同步機制有關,并發情況下,因為優先保證同步,是以并行的插入變成串行啦,就很耗時。

優化前:

優化前,1000筆先落地DB資料庫,再異步轉賬,如下:

一次接口性能優化實踐:接口性能從11s降到了100ms提升了百倍

優化後:

先儲存資料到檔案,再異步下載下傳下來,插入資料庫,如下:

一次接口性能優化實踐:接口性能從11s降到了100ms提升了百倍

解析:

  • 如果你的耗時瓶頸就在資料庫插入操作這裡了,那就考慮檔案儲存或者MQ或者其他方式暫存吧,檔案儲存資料,對比一下耗時,有時候會有意想不到的效果哦。

書寫高品質SQL的30條建議

1、查詢SQL盡量不要使用select *,而是select具體字段。

反例子:

select * from employee;複制代碼           

正例子:

select id,name from employee;複制代碼           

理由:

  • 隻取需要的字段,節省資源、減少網絡開銷。
  • select * 進行查詢時,很可能就不會使用到覆寫索引了,就會造成回表查詢。

2、如果知道查詢結果隻有一條或者隻要最大/最小一條記錄,建議用limit 1

假設現在有employee員工表,要找出一個名字叫jay的人.

CREATE TABLE `employee` (
  `id` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `date` datetime DEFAULT NULL,
  `sex` int(1) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;複制代碼           

反例:

select id,name from employee where name='jay'複制代碼           

正例

select id,name from employee where name='jay' limit 1;複制代碼           

理由:

  • 加上limit 1後,隻要找到了對應的一條記錄,就不會繼續向下掃描了,效率将會大大提高。
  • 當然,如果name是唯一索引的話,是不必要加上limit 1了,因為limit的存在主要就是為了防止全表掃描,進而提高性能,如果一個語句本身可以預知不用全表掃描,有沒有limit ,性能的差别并不大。

3、應盡量避免在where子句中使用or來連接配接條件

建立一個user表,它有一個普通索引userId,表結構如下:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userId` int(11) NOT NULL,
  `age` int(11) NOT NULL,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_userId` (`userId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;複制代碼           

假設現在需要查詢userid為1或者年齡為18歲的使用者,很容易有以下sql

反例:

select * from user where userid=1 or age =18複制代碼           

正例:

//使用union all 
select * from user where userid=1 
union all 
select * from user where age = 18

//或者分開兩條sql寫:
select * from user where userid=1
select * from user where age = 18複制代碼           

理由:

  • 使用or可能會使索引失效,進而全表掃描。
對于or+沒有索引的age這種情況,假設它走了userId的索引,但是走到age查詢條件時,它還得全表掃描,也就是需要三步過程: 全表掃描+索引掃描+合并 如果它一開始就走全表掃描,直接一遍掃描就完事。 mysql是有優化器的,處于效率與成本考慮,遇到or條件,索引可能失效,看起來也合情合理。

4、優化limit分頁

我們日常做分頁需求時,一般會用 limit 實作,但是當偏移量特别大的時候,查詢效率就變得低下。

反例:

select id,name,age from employee limit 10000,10複制代碼           

正例:

//方案一 :傳回上次查詢的最大記錄(偏移量)
select id,name from employee where id>10000 limit 10.

//方案二:order by + 索引
select id,name from employee order by id  limit 10000,10

//方案三:在業務允許的情況下限制頁數:複制代碼           

理由:

  • 當偏移量最大的時候,查詢效率就會越低,因為Mysql并非是跳過偏移量直接去取後面的資料,而是先把偏移量+要取的條數,然後再把前面偏移量這一段的資料抛棄掉再傳回的。
  • 如果使用優化方案一,傳回上次最大查詢記錄(偏移量),這樣可以跳過偏移量,效率提升不少。
  • 方案二使用order by+索引,也是可以提高查詢效率的。
  • 方案三的話,建議跟業務讨論,有沒有必要查這麼後的分頁啦。因為絕大多數使用者都不會往後翻太多頁。

5、優化你的like語句

日常開發中,如果用到模糊關鍵字查詢,很容易想到like,但是like很可能讓你的索引失效。

反例:

select userId,name from user where userId like '%123';複制代碼           

正例:

select userId,name from user where userId like '123%';複制代碼           

理由:

  • 把%放前面,并不走索引,如下:
  • 把% 放關鍵字後面,還是會走索引的。如下:

6、使用where條件限定要查詢的資料,避免傳回多餘的行

假設業務場景是這樣:查詢某個使用者是否是會員。曾經看過老的實作代碼是這樣。。。

反例:

List<Long> userIds = sqlMap.queryList("select userId from user where isVip=1");
boolean isVip = userIds.contains(userId);複制代碼           

正例:

Long userId = sqlMap.queryObject("select userId from user where userId='userId' and isVip='1' ")
boolean isVip = userId!=null;複制代碼           

理由:

  • 需要什麼資料,就去查什麼資料,避免傳回不必要的資料,節省開銷。

7、盡量避免在索引列上使用mysql的内置函數

業務需求:查詢最近七天内登陸過的使用者(假設loginTime加了索引)

反例:

select userId,loginTime from loginuser where Date_ADD(loginTime,Interval 7 DAY) >=now();複制代碼           

正例:

explain  select userId,loginTime from loginuser where  loginTime >= Date_ADD(NOW(),INTERVAL - 7 DAY);複制代碼           

理由:

  • 索引列上使用mysql的内置函數,索引失效
  • 如果索引列不加内置函數,索引還是會走的。

8、應盡量避免在 where 子句中對字段進行表達式操作,這将導緻系統放棄使用索引而進行全表掃

反例:

select * from user where age-1 =10;複制代碼           

正例:

select * from user where age =11;複制代碼           

理由:

  • 雖然age加了索引,但是因為對它進行運算,索引直接迷路了。。。

9、Inner join 、left join、right join,優先使用Inner join,如果是left join,左邊表結果盡量小

Inner join 内連接配接,在兩張表進行連接配接查詢時,隻保留兩張表中完全比對的結果集 left join 在兩張表進行連接配接查詢時,會傳回左表所有的行,即使在右表中沒有比對的記錄。 right join 在兩張表進行連接配接查詢時,會傳回右表所有的行,即使在左表中沒有比對的記錄。

都滿足SQL需求的前提下,推薦優先使用Inner join(内連接配接),如果要使用left join,左邊表資料結果盡量小,如果有條件的盡量放到左邊處理。

反例:

select * from tab1 t1 left join tab2 t2  on t1.size = t2.size where t1.id>2;複制代碼           

正例:

select * from (select * from tab1 where id >2) t1 left join tab2 t2 on t1.size = t2.size;複制代碼           

理由:

  • 如果inner join是等值連接配接,或許傳回的行數比較少,是以性能相對會好一點。
  • 同理,使用了左連接配接,左邊表資料結果盡量小,條件盡量放到左邊處理,意味着傳回的行數可能比較少。

10、應盡量避免在 where 子句中使用!=或<>操作符,否則将引擎放棄使用索引而進行全表掃描。

反例:

select age,name  from user where age <>18;複制代碼           

正例:

//可以考慮分開兩條sql寫
select age,name  from user where age <18;
select age,name  from user where age >18;複制代碼           

理由:

  • 使用!=和<>很可能會讓索引失效

11、使用聯合索引時,注意索引列的順序,一般遵循最左比對原則。

表結構:(有一個聯合索引idx_userid_age,userId在前,age在後)

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userId` int(11) NOT NULL,
  `age` int(11) DEFAULT NULL,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_userid_age` (`userId`,`age`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
複制代碼           

反例:

select * from user where age = 10;複制代碼           

正例:

//符合最左比對原則
select * from user where userid=10 and age =10;
//符合最左比對原則
select * from user where userid =10;複制代碼           

理由:

  • 當我們建立一個聯合索引的時候,如(k1,k2,k3),相當于建立了(k1)、(k1,k2)和(k1,k2,k3)三個索引,這就是最左比對原則。
  • 聯合索引不滿足最左原則,索引一般會失效,但是這個還跟Mysql優化器有關的。

12、對查詢進行優化,應考慮在 where 及 order by 涉及的列上建立索引,盡量避免全表掃描。

反例:

select * from user where address ='深圳' order by age ;複制代碼           

正例:

添加索引
alter table user add index idx_address_age (address,age)複制代碼           

13、如果插入資料過多,考慮批量插入。

反例:

for(User u :list){
 INSERT into user(name,age) values(#name#,#age#)   
}複制代碼           

正例:

//一次500批量插入,分批進行
insert into user(name,age) values
<foreach collection="list" item="item" index="index" separator=",">
    (#{item.name},#{item.age})
</foreach>複制代碼           

理由:

  • 批量插入性能好,更加省時間
打個比喻:假如你需要搬一萬塊磚到樓頂,你有一個電梯,電梯一次可以放适量的磚(最多放500),你可以選擇一次運送一塊磚,也可以一次運送500,你覺得哪個時間消耗大?

14、在适當的時候,使用覆寫索引。

覆寫索引能夠使得你的SQL語句不需要回表,僅僅通路索引就能夠得到所有需要的資料,大大提高了查詢效率。

反例:

// like模糊查詢,不走索引了
select * from user where userid like '%123%'複制代碼           

正例:

//id為主鍵,那麼為普通索引,即覆寫索引登場了。
select id,name from user where userid like '%123%';複制代碼           

15、慎用distinct關鍵字

distinct 關鍵字一般用來過濾重複記錄,以傳回不重複的記錄。在查詢一個字段或者很少字段的情況下使用時,給查詢帶來優化效果。但是在字段很多的時候使用,卻會大大降低查詢效率。

反例:

SELECT DISTINCT * from  user;複制代碼           

正例:

select DISTINCT name from user;複制代碼           

理由:

  • 帶distinct的語句cpu時間和占用時間都高于不帶distinct的語句。因為當查詢很多字段時,如果使用distinct,資料庫引擎就會對資料進行比較,過濾掉重複資料,然而這個比較,過濾的過程會占用系統資源,cpu時間。

16、删除備援和重複索引

反例:

KEY `idx_userId` (`userId`)  
  KEY `idx_userId_age` (`userId`,`age`)複制代碼           

正例:

//删除userId索引,因為組合索引(A,B)相當于建立了(A)和(A,B)索引
  KEY `idx_userId_age` (`userId`,`age`)複制代碼           

理由:

  • 重複的索引需要維護,并且優化器在優化查詢的時候也需要逐個地進行考慮,這會影響性能的。

17、如果資料量較大,優化你的修改/删除語句。

避免同時修改或删除過多資料,因為會造成cpu使用率過高,進而影響别人對資料庫的通路。

反例:

//一次删除10萬或者100萬+?
delete from user where id <100000;
//或者采用單一循環操作,效率低,時間漫長
for(User user:list){
   delete from user; 
}複制代碼           

正例:

//分批進行删除,如每次500
delete user where id<500
delete product where id>=500 and id<1000;複制代碼           

理由:

  • 一次性删除太多資料,可能會有lock wait timeout exceed的錯誤,是以建議分批操作。

18、where子句中考慮使用預設值代替null。

反例:

select * from user where age is not null;複制代碼           

正例:

//設定0為預設值
select * from user where age>0;複制代碼           

理由:

  • 并不是說使用了is null 或者 is not null 就會不走索引了,這個跟mysql版本以及查詢成本都有關。
如果mysql優化器發現,走索引比不走索引成本還要高,肯定會放棄索引,這些條件!=,>is null,is not null經常被認為讓索引失效,其實是因為一般情況下,查詢的成本高,優化器自動放棄的。
  • 如果把null值,換成預設值,很多時候讓走索引成為可能,同時,表達意思會相對清晰一點。

19、不要有超過5個以上的表連接配接

  • 連表越多,編譯的時間和開銷也就越大。
  • 把連接配接表拆開成較小的幾個執行,可讀性更高。
  • 如果一定需要連接配接很多表才能得到資料,那麼意味着糟糕的設計了。

20、exist & in的合理利用

假設表A表示某企業的員工表,表B表示部門表,查詢所有部門的所有員工,很容易有以下SQL:

select * from A where deptId in (select deptId from B);複制代碼           

這樣寫等價于:

先查詢部門表B

select deptId from B

再由部門deptId,查詢A的員工

select * from A where A.deptId = B.deptId

可以抽象成這樣的一個循環:

List<> resultSet ;
    for(int i=0;i<B.length;i++) {
          for(int j=0;j<A.length;j++) {
          if(A[i].id==B[j].id) {
             resultSet.add(A[i]);
             break;
          }
       }
    }複制代碼           

顯然,除了使用in,我們也可以用exists實作一樣的查詢功能,如下:

select * from A where exists (select 1 from B where A.deptId = B.deptId); 複制代碼           

因為exists查詢的了解就是,先執行主查詢,獲得資料後,再放到子查詢中做條件驗證,根據驗證結果(true或者false),來決定主查詢的資料結果是否得意保留。

那麼,這樣寫就等價于:

select * from A,先從A表做循環

select * from B where A.deptId = B.deptId,再從B表做循環.

同理,可以抽象成這樣一個循環:

List<> resultSet ;
    for(int i=0;i<A.length;i++) {
          for(int j=0;j<B.length;j++) {
          if(A[i].deptId==B[j].deptId) {
             resultSet.add(A[i]);
             break;
          }
       }
    }複制代碼           

資料庫最費勁的就是跟程式連結釋放。假設連結了兩次,每次做上百萬次的資料集查詢,查完就走,這樣就隻做了兩次;相反建立了上百萬次連結,申請連結釋放反複重複,這樣系統就受不了了。即mysql優化原則,就是小表驅動大表,小的資料集驅動大的資料集,進而讓性能更優。

是以,我們要選擇最外層循環小的,也就是,如果B的資料量小于A,适合使用in,如果B的資料量大于A,即适合選擇exist。

21、盡量用 union all 替換 union

如果檢索結果中不會有重複的記錄,推薦union all 替換 union。

反例:

select * from user where userid=1 
union  
select * from user where age = 10複制代碼           

正例:

select * from user where userid=1 
union all  
select * from user where age = 10複制代碼           

理由:

  • 如果使用union,不管檢索結果有沒有重複,都會嘗試進行合并,然後在輸出最終結果前進行排序。如果已知檢索結果沒有重複記錄,使用union all 代替union,這樣會提高效率。

22、索引不宜太多,一般5個以内。

  • 索引并不是越多越好,索引雖然提高了查詢的效率,但是也降低了插入和更新的效率。
  • insert或update時有可能會重建索引,是以建索引需要慎重考慮,視具體情況來定。
  • 一個表的索引數最好不要超過5個,若太多需要考慮一些索引是否沒有存在的必要。

23、盡量使用數字型字段,若隻含數值資訊的字段盡量不要設計為字元型

反例:

king_id` varchar(20) NOT NULL COMMENT '守護者Id'複制代碼           

正例:

`king_id` int(11) NOT NULL COMMENT '守護者Id'`複制代碼           

理由:

  • 相對于數字型字段,字元型會降低查詢和連接配接的性能,并會增加存儲開銷。

24、索引不适合建在有大量重複資料的字段上,如性别這類型資料庫字段。

因為SQL優化器是根據表中資料量來進行查詢優化的,如果索引列有大量重複資料,Mysql查詢優化器推算發現不走索引的成本更低,很可能就放棄索引了。

25、盡量避免向用戶端傳回過多資料量。

假設業務需求是,使用者請求檢視自己最近一年觀看過的直播資料。

反例:

//一次性查詢所有資料回來
select * from LivingInfo where watchId =useId and watchTime >= Date_sub(now(),Interval 1 Y)複制代碼           

正例:

//分頁查詢
select * from LivingInfo where watchId =useId and watchTime>= Date_sub(now(),Interval 1 Y) limit offset,pageSize

//如果是前端分頁,可以先查詢前兩百條記錄,因為一般使用者應該也不會往下翻太多頁,
select * from LivingInfo where watchId =useId and watchTime>= Date_sub(now(),Interval 1 Y) limit 200 ;複制代碼           

26、當在SQL語句中連接配接多個表時,請使用表的别名,并把别名字首于每一列上,這樣語義更加清晰。

反例:

select  * from A inner
join B on A.deptId = B.deptId;複制代碼           

正例:

select  memeber.name,deptment.deptName from A member inner
join B deptment on member.deptId = deptment.deptId;複制代碼           

27、盡可能使用varchar/nvarchar 代替 char/nchar。

反例:

`deptName` char(100) DEFAULT NULL COMMENT '部門名稱'複制代碼           

正例:

`deptName` varchar(100) DEFAULT NULL COMMENT '部門名稱'複制代碼           

理由:

  • 因為首先變長字段存儲空間小,可以節省存儲空間。
  • 其次對于查詢來說,在一個相對較小的字段内搜尋,效率更高。

28、為了提高group by 語句的效率,可以在執行到該語句前,把不需要的記錄過濾掉。

反例:

select job,avg(salary) from employee  group by job having job ='president' 
or job = 'managent'複制代碼           

正例:

select job,avg(salary) from employee where job ='president' 
or job = 'managent' group by job;複制代碼           

29、如何字段類型是字元串,where時一定用引号括起來,否則索引失效

反例:

select * from user where userid =123;複制代碼           

正例:

select * from user where userid ='123';複制代碼           

理由:

  • 為什麼第一條語句未加單引号就不走索引了呢? 這是因為不加單引号時,是字元串跟數字的比較,它們類型不比對,MySQL會做隐式的類型轉換,把它們轉換為浮點數再做比較。

30、使用explain 分析你SQL的計劃

日常開發寫SQL的時候,盡量養成一個習慣吧。用explain分析一下你寫的SQL,尤其是走不走索引這一塊。

explain select * from user where userid =10086 or age =18;複制代碼           

8.跟産品讨論需求最恰當,最舒服的實作方式

這點個人覺得還是很重要的,有些需求需要好好跟産品溝通的。

比如有個使用者連麥清單展示的需求,産品說要展示所有的連麥資訊,如果一個使用者的連麥清單資訊好大,你拉取所有連麥資料回來,接口性能就降下來啦。如果産品打樁分析,會發現,一般使用者看連麥清單,也就看前幾頁~是以,奸笑,哈哈~ 其實,那個超大分頁加載問題也是類似的。即limit +一個超大的數,一般會很慢的~~

繼續閱讀