您好,登錄后才能下訂單哦!
這期內(nèi)容當(dāng)中小編將會給大家?guī)碛嘘P(guān)mysql 5.7虛擬列功能怎么用,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
言歸正傳,為了實驗,我創(chuàng)建了如下表:
CREATE TABLE `T`( `id` int(11) NOT NULL, `a` int(11) DEFAUT NULL, PRIMARY KEY(`id`), KEY `a`(`a`) ) ENGINE=InnoDB;
該表有三個字段,其中用id是主鍵索引,a是普通索引。
首先SQL判斷一個語句是不是慢查詢語句,用的是語句的執(zhí)行時間。他把語句執(zhí)行時間跟long_query_time這個系統(tǒng)參數(shù)作比較,如果語句執(zhí)行時間比它還大,就會把這個語句記錄到慢查詢?nèi)罩纠锩?,這個參數(shù)的默認(rèn)值是10秒。當(dāng)然在生產(chǎn)上,我們不會設(shè)置這么大,一般會設(shè)置1秒,對于一些比較敏感的業(yè)務(wù),可能會設(shè)置一個比1秒還小的值。
語句執(zhí)行過程中有沒有用到表的索引,可以通過explain一個語句的輸出結(jié)果來看KEY的值不是NULL。
我們看下 explain select * from t;
的KEY結(jié)果是NULL
?。▓D一)
explain select * from t where id=2;
的KEY結(jié)果是PRIMARY,就是我們常說的使用了主鍵索引
?。▓D二)
explain select a from t;
的KEY結(jié)果是a,表示使用了a這個索引。
?。▓D三)
雖然后兩個查詢的KEY都不是NULL,但是最后一個實際上掃描了整個索引樹a。
假設(shè)這個表的數(shù)據(jù)量有100萬行,圖二的語句還是可以執(zhí)行很快,但是圖三就肯定很慢了。如果是更極端的情況,比如,這個數(shù)據(jù)庫上CPU壓力非常的高,那么可能第2個語句的執(zhí)行時間也會超過long_query_time,會進(jìn)入到慢查詢?nèi)罩纠锩妗?/p>
所以我們可以得出一個結(jié)論:是否使用索引和是否進(jìn)入慢查詢之間并沒有必然的聯(lián)系。使用索引只是表示了一個SQL語句的執(zhí)行過程,而是否進(jìn)入到慢查詢是由它的執(zhí)行時間決定的,而這個執(zhí)行時間,可能會受各種外部因素的影響。換句話來說,使用了索引你的語句可能依然會很慢。
那如果我們在更深層次的看這個問題,其實他還潛藏了一個問題需要澄清,就是什么叫做使用了索引。
我們都知道,InnoDB是索引組織表,所有的數(shù)據(jù)都是存儲在索引樹上面的。比如上面的表t,這個表包含了兩個索引,一個主鍵索引和一個普通索引。在InnoDB里,數(shù)據(jù)是放在主鍵索引里的。如圖所示:
可以看到數(shù)據(jù)都放在主鍵索引上,如果從邏輯上說,所有的InnoDB表上的查詢,都至少用了一個索引,所以現(xiàn)在我問你一個問題,如果你執(zhí)行select from t where id>0
,你覺得這個語句有用上索引嗎?
我們看上面這個語句的explain的輸出結(jié)果顯示的是PRIMARY。其實從數(shù)據(jù)上你是知道的,這個語句一定是做了全面掃描。但是優(yōu)化器認(rèn)為,這個語句的執(zhí)行過程中,需要根據(jù)主鍵索引,定位到第1個滿足ID>0的值,也算用到了索引。
所以即使explain的結(jié)果里寫的KEY不是NULL,實際上也可能是全表掃描的,因此InnoDB里面只有一種情況叫做沒有使用索引,那就是從主鍵索引的最左邊的葉節(jié)點開始,向右掃描整個索引樹。
也就是說,沒有使用索引并不是一個準(zhǔn)確的描述。
你可以用全表掃描來表示一個查詢遍歷了整個主鍵索引樹;
也可以用全索引掃描,來說明像select a from t;這樣的查詢,他掃描了整個普通索引樹;
而select * from t where id=2這樣的語句,才是我們平時說的使用了索引。他表示的意思是,我們使用了索引的快速搜索功能,并且有效的減少了掃描行數(shù)。
根據(jù)以上解剖,我們知道全索引掃描會讓查詢變慢,接下來就要來談?wù)勊饕倪^濾性。
假設(shè)你現(xiàn)在維護(hù)了一個表,這個表記錄了中國14億人的基本信息,現(xiàn)在要查出所有年齡在10~15歲之間的姓名和基本信息,那么你的語句會這么寫,select * from t_people where age between 10 and 15
。
你一看這個語句一定要在age字段上開始建立索引了,否則就是個全面掃描,但是你會發(fā)現(xiàn),在你建立索引以后,這個語句還是執(zhí)行慢,因為滿足這個條件的數(shù)據(jù)可能有超過1億行。
我們來看看建立索引以后,這個表的組織結(jié)構(gòu)圖:
這個語句的執(zhí)行流程是這樣的:
從索引上用樹搜索,取到第1個age等于10的記錄,得到它的主鍵id的值,根據(jù)id的值去主鍵索引取整行的信息,作為結(jié)果集的一部分返回;
在索引age上向右掃描,取下一個id的值,到主鍵索引上取整行信息,作為結(jié)果集的一部分返回;
重復(fù)上面的步驟,直到碰到第1個age大于15的記錄;
你看這個語句,雖然他用了索引,但是他掃描超過了1億行。所以你現(xiàn)在知道了,當(dāng)我們在討論有沒有使用索引的時候,其實我們關(guān)心的是掃描行數(shù)。
對于一個大表,不止要有索引,索引的過濾性還要足夠好。
像剛才這個例子的age,它的過濾性就不夠好,在設(shè)計表結(jié)構(gòu)的時候,我們要讓所有的過濾性足夠好,也就是區(qū)分度足夠高。
那么過濾性好了,是不是表示查詢的掃描行數(shù)就一定少呢?
我們再來看一個例子:
如果你的執(zhí)行語句是 select * from t_people where name='張三' and age=8
t_people表上有一個索引是姓名和年齡的聯(lián)合索引,那這個聯(lián)合索引的過濾性應(yīng)該不錯,可以在聯(lián)合索引上快速找到第1個姓名是張三,并且年齡是8的小朋友,當(dāng)然這樣的小朋友應(yīng)該不多,因此向右掃描的行數(shù)很少,查詢效率就很高。
但是查詢的過濾性和索引的過濾性可不一定是一樣的,如果現(xiàn)在你的需求是查出所有名字的第1個字是張,并且年齡是8歲的所有小朋友,你的語句會怎么寫呢?
你的語句要怎么寫?很顯然你會這么寫:select * from t_people where name like '張%' and age=8;
在MySQL5.5和之前的版本中,這個語句的執(zhí)行流程是這樣的:
首先從聯(lián)合索引上找到第1個年齡字段是張開頭的記錄,取出主鍵id,然后到主鍵索引樹上,根據(jù)id取出整行的值;
判斷年齡字段是否等于8,如果是就作為結(jié)果集的一行返回,如果不是就丟棄。
在聯(lián)合索引上向右遍歷,并重復(fù)做回表和判斷的邏輯,直到碰到聯(lián)合索引樹上名字的第1個字不是張的記錄為止。
我們把根據(jù)id到主鍵索引上查找整行數(shù)據(jù)這個動作,稱為回表。你可以看到這個執(zhí)行過程里面,最耗費時間的步驟就是回表,假設(shè)全國名字第1個字是張的人有8000萬,那么這個過程就要回表8000萬次,在定位第一行記錄的時候,只能使用索引和聯(lián)合索引的最左前綴,最稱為最左前綴原則。
你可以看到這個執(zhí)行過程,它的回表次數(shù)特別多,性能不夠好,有沒有優(yōu)化的方法呢?
在MySQL5.6版本,引入了index condition pushdown的優(yōu)化。我們來看看這個優(yōu)化的執(zhí)行流程:
首先從聯(lián)合索引樹上,找到第1個年齡字段是張開頭的記錄,判斷這個索引記錄里面,年齡的值是不是8,如果是就回表,取出整行數(shù)據(jù),作為結(jié)果集的一部分返回,如果不是就丟棄;
在聯(lián)合索引樹上,向右遍歷,并判斷年齡字段后,根據(jù)需要做回表,直到碰到聯(lián)合索引樹上名字的第1個字不是張的記錄為止;
這個過程跟上面的差別,是在遍歷聯(lián)合索引的過程中,將年齡等于8的條件下推到所有遍歷的過程中,減少了回表的次數(shù),假設(shè)全國名字第1個字是張的人里面,有100萬個是8歲的小朋友,那么這個查詢過程中在聯(lián)合索引里要遍歷8000萬次,而回表只需要100萬次。
可以看到這個優(yōu)化的效果還是很不錯的,但是這個優(yōu)化還是沒有繞開最左前綴原則的限制,因此在聯(lián)合索引你還是要掃描8000萬行,那有沒有更進(jìn)一步的優(yōu)化方法呢?
我們可以考慮把名字的第一個字和age來做一個聯(lián)合索引。這里可以使用MySQL5.7引入的虛擬列來實現(xiàn)。對應(yīng)的修改表結(jié)構(gòu)的SQL語句:
alter table t_people add name_first varchar(2) generated (left(name,1)),add index(name_first,age);
我們來看這個SQL語句的執(zhí)行效果:
CREATE TABLE `t_people`( `id` int(11) DEFAULT NULL, `name` varchar(20) DEFAUT NULL, `name_first` varchar(2) GENERATED ALWAYS AS (left(`name`,1)) VIRTUAL,KEY `name_first`(`name_first`,'age') ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
首先他在people上創(chuàng)建一個字段叫name_first的虛擬列,然后給name_first和age上創(chuàng)建一個聯(lián)合索引,并且,讓這個虛擬列的值總是等于name字段的前兩個字節(jié),虛擬列在插入數(shù)據(jù)的時候不能指定值,在更新的時候也不能主動修改,它的值會根據(jù)定義自動生成,在name字段修改的時候也會自動修改。
有了這個新的聯(lián)合索引,我們在找名字的第1個字是張,并且年齡為8的小朋友的時候,這個SQL語句就可以這么寫:select * from t_people where name_first=’張’ and age=8。
這樣這個語句的執(zhí)行過程,就只需要掃描聯(lián)合索引的100萬行,并回表100萬次,這個優(yōu)化的本質(zhì)是我們創(chuàng)建了一個更緊湊的索引,來加速了查詢的過程。
使用索引的語句也有可能是慢查詢,我們的查詢優(yōu)化的過程,往往就是減少掃描行數(shù)的過程。
慢查詢歸納起來大概有這么幾種情況:
全表掃描
全索引掃描
索引過濾性不好
頻繁回表的開銷
測試腳本
select VERSION(); -- 5.7 以后的功能
CREATE TABLE a_customer(
id BIGINT PRIMARY KEY,
billno varchar(20),
contactname VARCHAR(10),
companyname VARCHAR(50)
);
insert into a_customer
SELECT
tt.id,
tt.billno,
tt.contactname,
tt.companyname
FROM
tt_contactinfo tt;
alter table a_customer add productType varchar(10) generated always as (left(billno,7)) virtual;
alter table a_customer add firstName varchar(2) generated always as (left(contactname, 1)), add index(productType, firstname);
explain
select * from a_customer c where c.firstname='樂' and c.product = 'XS00013';
上述就是小編為大家分享的mysql 5.7虛擬列功能怎么用了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。