實驗目的:瞭解索引對於全列匹配,最左前綴匹配、範圍查詢的影響。實驗所用數據庫見文章最底部鏈接。html
實驗軟件版本:5.7.19-0ubuntu0.16.04.1-log (Ubuntu)
實驗存儲引擎:InnoDBmysql
show index from `employees`.`titles`
explain select * from `employees`.`titles` where `emp_no`='10001' and title='Senior Engineer' and `from_date`='1986-06-26';
很明顯,當按照索引中全部列進行精確匹配(這裏精確匹配指「=」或「IN」匹配)時,索引能夠被用到。這裏有一點須要注意,理論上索引對順序是敏感的,可是因爲MySQL的查詢優化器會自動調整where子句的條件順序以使用適合的索引。git
explain select * from `employees`.`titles` where `from_date`='1986-06-26' and `emp_no`='10001' and title='Senior Engineer';
explain select * from `employees`.`titles` where `emp_no`='10001';
當查詢條件精確匹配索引的左邊連續一個或幾個列時,如<emp_no>或<emp_no, title>,因此能夠被用到,可是隻能用到一部分,即條件所組成的最左前綴。上面的查詢從分析結果看用到了PRIMARY索引,可是key_len爲4,說明只用到了索引的第一列前綴。github
explain select * from `employees`.`titles` where `emp_no`='10001' and `from_date` = '1986-06-26' ;
此時索引使用狀況和實驗二相同,由於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`;
只有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';
此次key_len爲59,說明索引被用全了,可是從type和rows看出IN實際上執行了一個range查詢,這裏檢查了7個key。看下兩種查詢的性能比較:數據結構
「填坑」後性能提高了一點。若是通過emp_no篩選後餘下不少數據,則後者性能優點會更加明顯。固然,若是title的值不少,用填坑就不合適了,必須創建輔助索引。併發
explain select * from `employees`.`titles` where `from_date` = '1986-06-26';
因爲不是最左前綴,索引這樣的查詢顯然用不到索引。
explain select * from `employees`.`titles`where `emp_no` = '10001' and `title` like 'Senior%';
此時能夠用到索引。若是配符%不出如今開頭,則能夠用到索引,但根據具體狀況不一樣可能只會用其中一個前綴。
explain select * from `employees`.`titles` where `emp_no` < '10010' and `title` = 'Senior Engineer';
範圍列能夠用到索引(必須是最左前綴),可是範圍列後面的列沒法用到索引。同時,索引最多用於一個範圍列,所以若是查詢條件中有兩個範圍列則沒法全用到索引。
explain select * from `employees`.`titles` where `emp_no` < '10010' and `title` = 'Senior Engineer' and `from_date` between '1986-01-01' and '1986-12-11';
能夠看到索引對第二個範圍索引無能爲力。這裏特別要說明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';
看起來是用了兩個範圍查詢,但做用於emp_no上的「BETWEEN」實際上至關於「IN」,也就是說emp_no實際是多值精確匹配。能夠看到這個查詢用到了索引所有三個列。所以在MySQL中要謹慎地區分多值匹配和範圍匹配,不然會對MySQL的行爲產生困惑。
若是查詢條件中含有函數或表達式,則MySQL不會爲這列使用索引(雖然某些在數學意義上可使用)。例如:
explain select * from `employees`.`titles` where `emp_no` = '10001' and left(`title`, 6) = 'Senior';
雖然這個查詢和實驗五中功能相同,可是因爲使用了函數left,則沒法爲title列應用索引,而實驗五中用LIKE則能夠。再如:
explain select * from `employees`.`titles` where `emp_no` - 1 = '10000';
顯然這個查詢等價於查詢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`;
title的選擇性不足0.0001(精確值爲0.00001579),因此實在沒有什麼必要爲其單獨建索引。
有一種與索引選擇性有關的索引優化策略叫作前綴索引,就是用列的前綴代替整個列做爲索引key,當前綴長度合適時,能夠作到既使得前綴索引的選擇性接近全列索引,同時由於索引key變短而減小了索引文件的大小和維護開銷。
explain select * from `employees`.`employees` where `first_name` = 'Eric' and `last_name` = 'Anido';
由於employees表只有一個索引<emp_no>,那麼若是咱們想按名字搜索一我的,就只能全表掃描了:
若是頻繁按名字搜索員工,這樣顯然效率很低,所以咱們能夠考慮建索引。有兩種選擇,建<first_name>或<first_name, last_name>,看下兩個索引的選擇性:
select count(distinct(first_name))/count(*) as selectivity from `employees`.`employees`;
select count(distinct(concat(first_name, last_name)))/count(*) as selectivity from `employees`.`employees`;
<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`;
加索引
ALTER TABLE employees.employees ADD INDEX `first_name_last_name4` (first_name, last_name(4));
前綴索引兼顧索引大小和查詢速度,可是其缺點是不能用於ORDER BY和GROUP BY操做,也不能用於Covering index(即當索引自己包含查詢所需所有數據時,再也不訪問數據文件自己)。
實驗目的:瞭解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的數據,這就是所謂的髒讀。
定義:一個事務批量讀取了一批數據時,另外一個事務提交了新的數據,當以前的事務再次讀取時,會產生幻影行。
如丙存款100元未提交,這時銀行作報表統計account表中全部用戶的總額爲500元,而後丙提交了,這時銀行再統計發現賬戶爲600元了,形成虛讀一樣會使銀行不知所措,到底以哪一個爲準。
1.設置事物隔離級別。
set global transaction isolation level read committed; begin; select * from `employees`.`titles` where `titles`.`from_date` = '1994-12-15';
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;
定義:不可重複讀指在一個事務內讀取表中的某一行數據,屢次讀取結果不一樣。
例如銀行想查詢A賬戶餘額,第一次查詢A賬戶爲200元,此時A向賬戶內存了100元並提交了,銀行接着又進行了一次查詢,此時A賬戶爲300元了。銀行兩次查詢不一致,可能就會很困惑,不知道哪次查詢是準的。
不可重複讀和髒讀的區別是,髒讀是讀取前一事務未提交的髒數據,不可重複讀是從新讀取了前一事務已提交的數據。
不少人認爲這種狀況就對了,無須困惑,固然是後面的爲準。咱們能夠考慮這樣一種狀況,好比銀行程序須要將查詢結果分別輸出到電腦屏幕和寫到文件中,結果在一個事務中針對輸出的目的地,進行的兩次查詢不一致,致使文件和屏幕中的結果不一致,銀行工做人員就不知道以哪一個爲準了。
begin; select * from `employees`.`titles` where `emp_no` = 100001; select * from `employees`.`titles` where `emp_no` = 100001;
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;
隔離級別 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|
Read Uncommitted | ✅ | ✅ | ✅ |
Read Committed | ❌ | ✅ | ✅ |
Repeatable Read (默認) | ❌ | ❌ | ✅ |
Serializable | ❌ | ❌ | ❌ |