MySQL實驗: 實踐索引對全列匹配、最左前綴匹配、範圍查詢等條件的影響以及瞭解髒讀、幻讀等

索引實驗

實驗目的:瞭解索引對於全列匹配,最左前綴匹配、範圍查詢的影響。實驗所用數據庫見文章最底部鏈接。html

實驗軟件版本:5.7.19-0ubuntu0.16.04.1-log (Ubuntu)
實驗存儲引擎:InnoDBmysql

show index from `employees`.`titles`

clipboard.png

實驗1、全列匹配

explain select * from `employees`.`titles` where `emp_no`='10001' and title='Senior Engineer' and `from_date`='1986-06-26';

clipboard.png

很明顯,當按照索引中全部列進行精確匹配(這裏精確匹配指「=」或「IN」匹配)時,索引能夠被用到。這裏有一點須要注意,理論上索引對順序是敏感的,可是因爲MySQL的查詢優化器會自動調整where子句的條件順序以使用適合的索引。git

explain select * from `employees`.`titles` where `from_date`='1986-06-26' and `emp_no`='10001' and title='Senior Engineer';

clipboard.png

實驗2、最左前綴匹配

explain select * from `employees`.`titles` where `emp_no`='10001';

clipboard.png

當查詢條件精確匹配索引的左邊連續一個或幾個列時,如<emp_no>或<emp_no, title>,因此能夠被用到,可是隻能用到一部分,即條件所組成的最左前綴。上面的查詢從分析結果看用到了PRIMARY索引,可是key_len爲4,說明只用到了索引的第一列前綴。github

實驗3、查詢條件用到了索引中列的精確匹配,可是中間某個條件未提供

explain select * from `employees`.`titles` where `emp_no`='10001' and `from_date` = '1986-06-26' ;

clipboard.png

此時索引使用狀況和實驗二相同,由於title未提供,因此查詢只用到了索引的第一列,然後面的from_date雖然也在索引中,可是因爲title不存在而沒法和左前綴鏈接,所以須要對結果進行掃描過濾from_date(這裏因爲emp_no惟一,因此不存在掃描)。算法

若是想讓from_date也使用索引而不是where過濾,能夠增長一個輔助索引<emp_no, from_date>,此時上面的查詢會使用這個索引。除此以外,還可使用一種稱之爲「隔離列」的優化方法,將emp_no與from_date之間的「坑」填上。sql

看下title一共有幾種不一樣的值。數據庫

select distinct(title) from `employees`.`titles`;

clipboard.png

只有7種。在這種成爲「坑」的列值比較少的狀況下,能夠考慮用「IN」來填補這個「坑」從而造成最左前綴:ubuntu

explain select * from `employees`.`titles`
where `emp_no` = '10001'
and `title` IN ('Senior Engineer', 'Staff', 'Engineer', 'Senior Staff', 'Assistant Engineer', 'Technique Leader', 'Manager')
and `from_date` = '1986-06-26';

clipboard.png

此次key_len爲59,說明索引被用全了,可是從type和rows看出IN實際上執行了一個range查詢,這裏檢查了7個key。看下兩種查詢的性能比較:數據結構

clipboard.png

「填坑」後性能提高了一點。若是通過emp_no篩選後餘下不少數據,則後者性能優點會更加明顯。固然,若是title的值不少,用填坑就不合適了,必須創建輔助索引。併發

實驗四:查詢條件沒有指定索引第一列

explain select * from `employees`.`titles` where `from_date` = '1986-06-26';

clipboard.png

因爲不是最左前綴,索引這樣的查詢顯然用不到索引。

實驗五:匹配某列的前綴字符串

explain select * from `employees`.`titles`where `emp_no` = '10001' and `title` like 'Senior%';

clipboard.png

此時能夠用到索引。若是配符%不出如今開頭,則能夠用到索引,但根據具體狀況不一樣可能只會用其中一個前綴。

實驗六:範圍查詢

explain select * from `employees`.`titles` where `emp_no` < '10010' and `title` = 'Senior Engineer';

clipboard.png

範圍列能夠用到索引(必須是最左前綴),可是範圍列後面的列沒法用到索引。同時,索引最多用於一個範圍列,所以若是查詢條件中有兩個範圍列則沒法全用到索引。

explain select * from `employees`.`titles`
where `emp_no` < '10010'
and `title` = 'Senior Engineer'
and `from_date` between '1986-01-01' and '1986-12-11';

clipboard.png

能夠看到索引對第二個範圍索引無能爲力。這裏特別要說明MySQL一個有意思的地方,那就是僅用explain可能沒法區分範圍索引和多值匹配,由於在type中這二者都顯示爲range。同時,用了「between」並不意味着就是範圍查詢,例以下面的查詢:

explain select * from `employees`.`titles`
where `emp_no` between '10001' and '10010'
and `title` = 'Senior Enginee'
and `from_date` between '1986-01-01' and '1986-12-31';

clipboard.png

看起來是用了兩個範圍查詢,但做用於emp_no上的「BETWEEN」實際上至關於「IN」,也就是說emp_no實際是多值精確匹配。能夠看到這個查詢用到了索引所有三個列。所以在MySQL中要謹慎地區分多值匹配和範圍匹配,不然會對MySQL的行爲產生困惑。

實驗七:查詢條件中含有函數或表達式

若是查詢條件中含有函數或表達式,則MySQL不會爲這列使用索引(雖然某些在數學意義上可使用)。例如:

explain select * from `employees`.`titles` where `emp_no` = '10001' and left(`title`, 6) = 'Senior';

clipboard.png

雖然這個查詢和實驗五中功能相同,可是因爲使用了函數left,則沒法爲title列應用索引,而實驗五中用LIKE則能夠。再如:

explain select * from `employees`.`titles` where `emp_no` - 1 = '10000';

clipboard.png

顯然這個查詢等價於查詢emp_no爲10001的函數,可是因爲查詢條件是一個表達式,MySQL沒法爲其使用索引。所以在寫查詢語句時儘可能避免表達式出如今查詢中,而是先手工私下代數運算,轉換爲無表達式的查詢語句。

索引選擇性與前綴索引

索引選擇性

所謂索引的選擇性(Selectivity),是指不重複的索引值(也叫基數,Cardinality)與表記錄數(#T)的比值:

Index Selectivity = Cardinality / #T

顯然選擇性的取值範圍爲(0, 1],選擇性越高的索引價值越大,這是由B+Tree的性質決定的。例如,上文用到的employees.titles表,若是title字段常常被單獨查詢,是否須要建索引,咱們看一下它的選擇性:

select count(distinct(title))/count(*) as selectivity from `employees`.`titles`;

clipboard.png

title的選擇性不足0.0001(精確值爲0.00001579),因此實在沒有什麼必要爲其單獨建索引。

前綴索引

有一種與索引選擇性有關的索引優化策略叫作前綴索引,就是用列的前綴代替整個列做爲索引key,當前綴長度合適時,能夠作到既使得前綴索引的選擇性接近全列索引,同時由於索引key變短而減小了索引文件的大小和維護開銷。

explain select * from `employees`.`employees` where `first_name` = 'Eric' and `last_name` = 'Anido';

由於employees表只有一個索引<emp_no>,那麼若是咱們想按名字搜索一我的,就只能全表掃描了:

clipboard.png

若是頻繁按名字搜索員工,這樣顯然效率很低,所以咱們能夠考慮建索引。有兩種選擇,建<first_name>或<first_name, last_name>,看下兩個索引的選擇性:

select count(distinct(first_name))/count(*) as selectivity from `employees`.`employees`;

clipboard.png

select count(distinct(concat(first_name, last_name)))/count(*) as selectivity from `employees`.`employees`;

clipboard.png

<first_name>顯然選擇性過低,<first_name, last_name>選擇性很好,可是first_name和last_name加起來長度爲30,有沒有兼顧長度和選擇性的辦法?能夠考慮用first_name和last_name的前幾個字符創建索引,例如<first_name, left(last_name, 4)>,看看其選擇性:

select count(distinct(concat(first_name, left(last_name, 4))))/count(*) as selectivity from `employees`.`employees`;

clipboard.png

加索引

ALTER TABLE employees.employees
ADD INDEX `first_name_last_name4` (first_name, last_name(4));

前綴索引兼顧索引大小和查詢速度,可是其缺點是不能用於ORDER BY和GROUP BY操做,也不能用於Covering index(即當索引自己包含查詢所需所有數據時,再也不訪問數據文件自己)。

MySQL事務隔離層級實驗

實驗目的:瞭解MySQL中事務隔離級別以及什麼是髒讀,幻讀,不可重複讀。

實驗一:髒讀

定義:在兩個事務中,一個事務讀到了另外一個事務未提交的數據。由於數據可能被回滾,不符合隔離性的定義。

1.新建數據庫鏈接執行一下操做

set global transaction isolation level read uncommitted;
set autocommit = 0;
begin;
update `employees`.`titles` set `title` = 'Senior Engineer 1' where `emp_no` = 100001;

注意尚未執行 commit

2.而後新建一個鏈接 能夠看到讀到了另外一個事物還未被commit的數據,這就是所謂的髒讀。
clipboard.png

實驗二:幻讀

定義:一個事務批量讀取了一批數據時,另外一個事務提交了新的數據,當以前的事務再次讀取時,會產生幻影行。

如丙存款100元未提交,這時銀行作報表統計account表中全部用戶的總額爲500元,而後丙提交了,這時銀行再統計發現賬戶爲600元了,形成虛讀一樣會使銀行不知所措,到底以哪一個爲準。

1.設置事物隔離級別。

set global transaction isolation level read committed;
begin;
select * from `employees`.`titles` where `titles`.`from_date` = '1994-12-15';

clipboard.png

2.新開一個鏈接

begin;
insert into `titles` values (499999, 'Engineer', '1994-12-15', '1994-12-15');
commit;

3.回到第一步的窗口,查詢數據。

select * from `employees`.`titles` where `titles`.`from_date` = '1994-12-15';
commit;

clipboard.png

實驗三:不可重複讀

定義:不可重複讀指在一個事務內讀取表中的某一行數據,屢次讀取結果不一樣。

例如銀行想查詢A賬戶餘額,第一次查詢A賬戶爲200元,此時A向賬戶內存了100元並提交了,銀行接着又進行了一次查詢,此時A賬戶爲300元了。銀行兩次查詢不一致,可能就會很困惑,不知道哪次查詢是準的。
  不可重複讀和髒讀的區別是,髒讀是讀取前一事務未提交的髒數據,不可重複讀是從新讀取了前一事務已提交的數據。
  不少人認爲這種狀況就對了,無須困惑,固然是後面的爲準。咱們能夠考慮這樣一種狀況,好比銀行程序須要將查詢結果分別輸出到電腦屏幕和寫到文件中,結果在一個事務中針對輸出的目的地,進行的兩次查詢不一致,致使文件和屏幕中的結果不一致,銀行工做人員就不知道以哪一個爲準了。

  1. 開啓鏈接查詢值。
begin;
select * from `employees`.`titles` where `emp_no` = 100001;
select * from `employees`.`titles` where `emp_no` = 100001;

clipboard.png

2.新開一個鏈接修改emp_no爲100001的title的值。

begin;
update `employees`.`titles` set `title` = 'Senior Engineer 1' where `emp_no` = 100001;
commit;

3.回到第一步的鏈接再次查詢

select * from `employees`.`titles` where `emp_no` = 100001;

clipboard.png

MySQL事務隔離級別

  • 未提交讀:第一個事務還未提交,另外一個事務就能夠讀取,致使髒讀。
  • 提交讀(不可重複讀):一個事務未提交對其餘事務不可見,可是會產生幻讀和不可重複讀。
  • 可重複讀(mysql默認隔離級別):保證同一個事務下屢次讀取的結果一致,可是會產生幻讀。
  • 可串行化:嚴格的串行阻塞,併發能力很差。
隔離級別 髒讀 不可重複讀 幻讀
Read Uncommitted
Read Committed
Repeatable Read (默認)
Serializable

參考資料

1.走進mysql基礎
2.MySQL索引背後的數據結構及算法原理
3.datacharmer/test_db

相關文章
相關標籤/搜索