Mysql的幾個(gè)靈魂拷問(四)

Mysql前面已經(jīng)把基礎(chǔ)和原理部分鋪墊的差不多了,現(xiàn)在要來講講的是Sql優(yōu)化和調(diào)優(yōu)部分了,這個(gè)基本是Mysql拷問系列最直接靈魂的環(huán)節(jié)了,優(yōu)化這幾乎是每場(chǎng)面試對(duì)話的最高潮部分,但這里又是一個(gè)開放的話題,能不能綻放出亮點(diǎn),就看平時(shí)在調(diào)優(yōu)方面下的功夫夠不夠了,數(shù)據(jù)庫優(yōu)化維度有四個(gè):硬件、系統(tǒng)配置、數(shù)據(jù)庫表結(jié)構(gòu)、SQL及索引。這一篇的重點(diǎn)是放在SQL語句的優(yōu)化上,表設(shè)計(jì)與大表優(yōu)化方案則放在下一個(gè)篇文章中。

一、總優(yōu)化思路

對(duì)于程序開發(fā)者來說,MySQL層優(yōu)化一般可以遵從五個(gè)原則:

  • 減少數(shù)據(jù)訪問:設(shè)置合理的字段類型,啟用壓縮,通過索引訪問等減少磁盤 IO。
  • 返回更少的數(shù)據(jù):只返回需要的字段和數(shù)據(jù)分頁處理,減少磁盤 IO 及網(wǎng)絡(luò) IO。
  • 減少交互次數(shù):批量 DML 操作,函數(shù)存儲(chǔ)等減少數(shù)據(jù)連接次數(shù)。
  • 減少服務(wù)器 CPU 開銷:盡量減少數(shù)據(jù)庫排序操作以及全表查詢,減少 CPU 內(nèi)存占用
  • 利用更多資源:使用表分區(qū),可以增加并行操作,更大限度利用 CPU 資源。

總結(jié)到 SQL 優(yōu)化中,就如下三點(diǎn):

  • 最大化利用索引。
  • 盡可能避免全表掃描。
  • 減少無效數(shù)據(jù)的查詢。

二、索引優(yōu)化策略


眾所周知,索引類似于字典的目錄,可以提高查詢的效率。索引從物理上可以分為:聚集索引,非聚集索引。從邏輯上可以分為:普通索引,唯一索引,主鍵索引,聯(lián)合索引,全文索引。這一部分主要總結(jié)Sql優(yōu)化的一些技巧和規(guī)范,尤其對(duì)于一些還有歧義和誤導(dǎo)性的說法做一個(gè)糾正。

1、合理建立覆蓋索引

合理的覆蓋索引,可以減少回表次數(shù)。表數(shù)據(jù)是放在一個(gè)聚集索引上的,而建立的索引為非聚集索引,非聚集索引的葉子節(jié)點(diǎn)存放索引鍵值,以及該索引鍵指向的主鍵。一般查找的過程是從非聚集索引上找到數(shù)據(jù)的主鍵,然后根據(jù)該主鍵到聚集索引上查找記錄,這個(gè)過程稱為回表。

如有下面這個(gè)sql

select uid, login_time from user where username = ? and passwd = ?

可以建立(username, passwd, login_time)的聯(lián)合索引,由于 login_time的值可以直接從索引中拿到,不用再回表查詢,提高了查詢效率。

建立聯(lián)合索引的時(shí)候注意將區(qū)分度最高的字段放在最左邊!

2、union,or,in都能命中索引,建議使用in

新版Mysql中union,or,in都是會(huì)走索引的,所以如下三條sql語句都是等價(jià)的:

select * from article where id = 1
union all
select * from article where id = 2

select * from article where id in (1 , 2)

select * from article where id = 1 or id = 2

效率從高到低為union,in,or。in和union的效率差別可以忽略不計(jì),所以直接建議使用in。union 比 union all 多一個(gè)去重邏輯,業(yè)務(wù)上知道已經(jīng)有字段有唯一屬性的話,直接用union all 會(huì)比 union 效率更高點(diǎn)兒。

exist和in的差別,并不是exist替換都比in要好:

  • 子查詢使用 exists,會(huì)先進(jìn)行主查詢,將查詢到的每行數(shù)據(jù)循環(huán)帶入子查詢校驗(yàn)是否存在,過濾出整體的返回?cái)?shù)據(jù);子查詢使用 in,會(huì)先進(jìn)行子查詢獲取結(jié)果集,然后主查詢匹配子查詢的結(jié)果集,返回?cái)?shù)據(jù)
  • 外表內(nèi)表相對(duì)大小情況不一樣時(shí),查詢效率不一樣:兩表大小相當(dāng),in 和 exists 差別不大;內(nèi)表大,用 exists 效率較高;內(nèi)表小,用 in 效率較高。
  • 不管外表與內(nèi)表的大小,not exists 的效率一般要高于 not in,跟子查詢的索引訪問類型有關(guān)。

負(fù)向條件索引不會(huì)使用索引,建議用in

負(fù)向條件有:!=、<>、not in、not exists、not like 等

-- 全表掃描
select * from article where id != 1 and id != 2

知道id的所有取值范圍,可以改為類似如下形式

-- 走索引
select * from article where id in (0, 3, 4)

在索引列上進(jìn)行運(yùn)算或使用函數(shù)會(huì)走全表

在列上進(jìn)行運(yùn)算或使用函數(shù)會(huì)使索引失效,從而進(jìn)行全表掃描。如下面例子在publish_time,id列上分別加上索引,publish_time為datetime類型,id為int類型:

-- 全表掃描
select * from article where year(publish_time) < 2019
-- 走索引
select * from article where publish_time < '2019-01-01'
-- 全表掃描
select * from article where id  + 1 = 5
-- 走索引
select * from article where id = 4

小心隱式類型轉(zhuǎn)換

假設(shè)id為varchar類型,用int類型查會(huì)走全表。

-- 全表掃描
select * from article where id = 100
-- 走索引
select * from article where id = '100'

隱式類型轉(zhuǎn)換在索引字段上做了函數(shù)操作,因此會(huì)全表掃描。

select * from article where id = 100
-- 等價(jià)于
select * from article where CAST(id AS signed int) = 100

那么如果id是int,執(zhí)行下面這個(gè)語句是否會(huì)導(dǎo)致全表掃描呢?select * from article where id = '100',答案是會(huì)用到索引。

不建議使用%前綴模糊查詢

%李,%李%都會(huì)導(dǎo)致全表掃描,非前導(dǎo)模糊查詢可以使用索引

避免在where子句中進(jìn)行null值判斷

對(duì)于null的判斷會(huì)導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描。

經(jīng)常更改,區(qū)分度不高的列上不宜加索引

  • 更新會(huì)變更 B+ 樹,更新頻繁的字段建立索引會(huì)大大降低數(shù)據(jù)庫性能。
  • 一般區(qū)分度在80%以上的時(shí)候就可以建立索引。

區(qū)分度可以使用 count(distinct(列名))/count(*) 來計(jì)算
“性別”這種區(qū)分度不大的屬性,建立索引是沒有什么意義的,不能有效過濾數(shù)據(jù),性能與全表掃描類似。

多表關(guān)聯(lián)查詢時(shí),小表在前,大表在后

在 MySQL 中,執(zhí)行 from 后的表關(guān)聯(lián)查詢是從左往右執(zhí)行的(Oracle 相反),第一張表會(huì)涉及到全表掃描。

調(diào)整 Where 字句中的連接順序

MySQL 采用從左往右,自上而下的順序解析 where 子句。根據(jù)這個(gè)原理,應(yīng)將過濾數(shù)據(jù)多的條件往前放,最快速度縮小結(jié)果集。

查詢是否存在可以加limit1

查詢是否存在或者當(dāng)查詢確定只有一條記錄時(shí),可以加liimit1,讓MySQL停止游標(biāo)移動(dòng),提高查詢效率

select uid from user where username = ? and passwd = ?

可改為:

select uid from user where username = ? and passwd = ? limit 1

Explain調(diào)優(yōu)


說了這么多的優(yōu)化總結(jié),最重要的可能還是通過分析手段去,使用explain關(guān)鍵字可以模擬優(yōu)化器執(zhí)行SQL語句,號(hào)稱SQL中的debug。通過explain可以知道m(xù)ysql是如何處理sql語句,分析查詢語句或者表結(jié)構(gòu)的性能瓶頸。

explain + SQL

explain包含很多的Item,主要關(guān)注type,possible_key,key,row和Extra。
id:執(zhí)行權(quán)重,查詢多張表后對(duì)應(yīng)的id越大優(yōu)先級(jí)越高
type:查詢使用的類型,system > const > eq_ref > ref > range > index > all

        system 只有一行數(shù)據(jù),一般不出現(xiàn)
        const 通過索引一次就找到
        eq_ref  唯一性索引掃描
        ref  非唯一性索引掃描
        range 檢索制定范圍
        index 只遍歷索引樹
        all 全表掃描

table:表名
possible_key:表中存在的索引
key:本次查詢使用的索引
row:找到目標(biāo)大致所需的行數(shù)
Extra:

        using filesort 使用外部的索引排序
        using temporary 使用臨時(shí)表保存數(shù)據(jù)
        using index 使用了覆蓋索引
        using where 使用where條件
        using join buffer 使用連接緩存
        impossible  where where條件無結(jié)果
        select tables optimized away 最佳優(yōu)化狀態(tài),無需遍歷索引

優(yōu)化思路還是沿著我們上面總結(jié)的那些來進(jìn)行即可。

  • 使用 explain 分析語句時(shí)若發(fā)現(xiàn) rows 非常大的查詢語句可以考慮在對(duì)應(yīng)的關(guān)鍵詞上加入索引
  • 用聯(lián)合索引來減少回標(biāo)次數(shù)
  • 如果列類型是字符串,那一定要在條件中將數(shù)據(jù)使用引號(hào)引用起來,否則不會(huì)使用索引
  • 應(yīng)盡量避免在 where 子句中使用!=<> not in、not exisit等負(fù)向條件的操作符,改成in之類。
  • 避免在where后面用函數(shù)或者計(jì)算,走全表了
  • 避免在where后面做null值判斷
  • 在搜索字符型字段時(shí),我們有時(shí)會(huì)使用LIKE關(guān)鍵字和通配符,避免前綴也是模糊。

參考引用


1、這次被問懵了!搞定了這些SQL優(yōu)化技巧,下次橫著走
2、MySQL索引優(yōu)化實(shí)戰(zhàn)
3、巧用 explain 優(yōu)化 MySQL 語句

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容