1. 程式人生 > >解讀mysql的索引和事務的正確姿勢

解讀mysql的索引和事務的正確姿勢

一、索引是做什麼的?

很多時候,當你的應用程式進行SQL查詢速度很慢時,應該想想是否可以建索引。

大多數MySQL索引(PRIMARY KEY、UNIQUE、INDEX和FULLTEXT)在B樹中儲存。只是空間列型別的索引使用R-樹,並且MEMORY表還支援hash索引。

索引是一個排序的列表,在這個列表中儲存著索引的值和包含這個值的資料所在行的實體地址,在資料十分龐大的時候,索引可以大大加快查詢的速度,這是因為使用索引後可以不用掃描全表來定位某行的資料,而是先通過索引表找到該行資料對應的實體地址然後訪問相應的資料。

二、索引的優缺點

優勢:可以快速檢索,減少I/O次數,加快檢索速度;根據索引分組和排序,可以加快分組和排序;

劣勢:索引本身也是表,因此會佔用儲存空間,一般來說,索引表佔用的空間的資料表的1.5倍;索引表的維護和建立需要時間成本,這個成本隨著資料量增大而增大;構建索引會降低資料表的修改操作(刪除,新增,修改)的效率,因為在修改資料表的同時還需要修改索引表;

三、索引的分類

常見的索引型別有:主鍵索引、唯一索引、普通索引、全文索引、組合索引

1、主鍵索引:即主索引,根據主鍵pk_clolum(length)建立索引,不允許重複,不允許空值;

ALTER TABLE 'table_name' ADD PRIMARY KEY('id');
複製程式碼

2、唯一索引:用來建立索引的列的值必須是唯一的,允許空值

ALTER TABLE 'table_name' ADD UNIQUE('email');
複製程式碼

3、普通索引:用表中的普通列構建的索引,沒有任何限制

ALTER TABLE 'table_name' ADD INDEX index_name('description');
複製程式碼

4、全文索引:用大文字物件的列構建的索引(下一部分會講解)

ALTER TABLE 'table_name' ADD FULLTEXT('content');
複製程式碼

5、組合索引:用多個列組合構建的索引,這多個列中的值不允許有空值

ALTER TABLE 'table_name' ADD INDEX index_name('col1'
,'col2','col3'); 複製程式碼

遵循“最左字首”原則,把最常用作為檢索或排序的列放在最左,依次遞減,組合索引相當於建立了col1,col1col2,col1col2col3三個索引,而col2或者col3是不能使用索引的。

在使用組合索引的時候可能因為列名長度過長而導致索引的key太大,導致效率降低,在允許的情況下,可以只取col1和col2的前幾個字元作為索引

ALTER TABLE 'table_name' ADD INDEX index_name(col1(4),col2(3));
複製程式碼

表示使用col1的前4個字元和col2的前3個字元作為索引

四、索引的實現原理

MySQL支援諸多儲存引擎,而各種儲存引擎對索引的支援也各不相同,因此MySQL資料庫支援多種索引型別,如BTree索引,B+Tree索引,雜湊索引,全文索引等等,

1、雜湊索引:

只有memory(記憶體)儲存引擎支援雜湊索引,雜湊索引用索引列的值計算該值的hashCode,然後在hashCode相應的位置存執該值所在行資料的物理位置,因為使用雜湊演算法,因此訪問速度非常快,但是一個值只能對應一個hashCode,而且是雜湊的分佈方式,因此雜湊索引不支援範圍查詢和排序的功能。

2、全文索引:

FULLTEXT(全文)索引,僅可用於MyISAM和InnoDB,針對較大的資料,生成全文索引非常的消耗時間和空間。對於文字的大物件,或者較大的CHAR型別的資料,如果使用普通索引,那麼匹配文字前幾個字元還是可行的,但是想要匹配文字中間的幾個單詞,那麼就要使用LIKE %word%來匹配,這樣需要很長的時間來處理,響應時間會大大增加,這種情況,就可使用時FULLTEXT索引了,在生成FULLTEXT索引時,會為文字生成一份單詞的清單,在索引時及根據這個單詞的清單來索引。FULLTEXT可以在建立表的時候建立,也可以在需要的時候用ALTER或者CREATE INDEX來新增:

//建立表的時候新增FULLTEXT索引
CTREATE TABLE my_table(
id INT(10) PRIMARY KEY,
name VARCHAR(10) NOT NULL,
my_text text CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
FULLTEXT(my_text));

//建立表以後,在需要的時候新增FULLTEXT索引
ALTER my_table ADD FULLTEXT ft_index(my_text);
CREATE INDEX ft_index ON my_table(my_text);
複製程式碼

對於較大的資料集,把資料新增到一個沒有FULLTEXT索引的表,然後新增FULLTEXT索引的速度比把資料新增到一個已經有FULLTEXT索引的錶快。

MySQL自帶的全文索引只能用於MyISAM儲存引擎,如果是其它資料引擎,那麼全文索引不會生效。

在MySQL中,全文索引支隊英文有用,目前對中文還不支援。

在MySQL中,如果檢索的字串太短則無法檢索得到預期的結果,檢索的字串長度至少為4位元組,此外,如果檢索的字元包括停止詞,那麼停止詞會被忽略。

3、BTree索引和B+Tree索引

BTree索引

BTree是平衡搜尋多叉樹,設樹的度為d(d>1),高度為h,那麼BTree要滿足以一下條件:

每個葉子結點的高度一樣,等於h;

每個非葉子結點由n-1個key和n個指標point組成,其中d<=n<=2d,key和point相互間隔,結點兩端一定是key;

葉子結點指標都為null;

非葉子結點的key都是[key,data]二元組,其中key表示作為索引的鍵,data為鍵值所在行的資料;

BTree的結構如下:

image

在BTree的機構下,就可以使用二分查詢的查詢方式,查詢複雜度為h*log(n),一般來說樹的高度是很小的,一般為3左右,因此BTree是一個非常高效的查詢結構。

B+Tree索引

B+Tree是BTree的一個變種,設d為樹的度數,h為樹的高度,B+Tree和BTree的不同主要在於:

B+Tree中的非葉子結點不儲存資料,只儲存鍵值;

B+Tree的葉子結點沒有指標,所有鍵值都會出現在葉子結點上,且key儲存的鍵值對應的資料的實體地址;

B+Tree的結構如下:

image

一般來說B+Tree比BTree更適合實現外存的索引結構,因為儲存引擎的設計專家巧妙的利用了外存(磁碟)的儲存結構,即磁碟的一個扇區是整數倍的page(頁),頁是儲存中的一個單位,通常預設為4K,因此索引結構的節點被設計為一個頁的大小,然後利用外存的“預讀取”原則,每次讀取的時候,把整個節點的資料讀取到記憶體中,然後在記憶體中查詢,已知記憶體的讀取速度是外存讀取I/O速度的幾百倍,那麼提升查詢速度的關鍵就在於儘可能少的磁碟I/O,那麼可以知道,每個節點中的key個數越多,那麼樹的高度越小,需要I/O的次數越少,因此一般來說B+Tree比BTree更快,因為B+Tree的非葉節點中不儲存data,就可以儲存更多的key。

帶順序索引的B+TREE

很多儲存引擎在B+Tree的基礎上進行了優化,添加了指向相鄰葉節點的指標,形成了帶有順序訪問指標的B+Tree,這樣做是為了提高區間查詢的效率,只要找到第一個值那麼就可以順序的查詢後面的值。

B+Tree的結構如下:

image

分析了MySQL的索引結構的實現原理,然後我們來看看具體的儲存引擎怎麼實現索引結構的,MySQL中最常見的兩種儲存引擎分別是MyISAM和InnoDB,分別實現了非聚簇索引和聚簇索引。

首先要介紹幾個概念,在索引的分類中,我們可以按照索引的鍵是否為主鍵來分為“主索引”和“輔助索引”,使用主鍵鍵值建立的索引稱為“主索引”,其它的稱為“輔助索引”。因此主索引只能有一個,輔助索引可以有很多個。

MyISAM——非聚簇索引

MyISAM儲存引擎採用的是非聚簇索引,非聚簇索引的主索引和輔助索引幾乎是一樣的,只是主索引不允許重複,不允許空值,他們的葉子結點的key都儲存指向鍵值對應的資料的實體地址。

非聚簇索引的資料表和索引表是分開儲存的。

非聚簇索引中的資料是根據資料的插入順序儲存。因此非聚簇索引更適合單個數據的查詢。插入順序不受鍵值影響。

只有在MyISAM中才能使用FULLTEXT索引。

最開始我一直不懂既然非聚簇索引的主索引和輔助索引指向相同的內容,為什麼還要輔助索引這個東西呢,後來才明白索引不就是用來查詢的嗎,用在那些地方呢,不就是WHERE和ORDER BY 語句後面嗎,那麼如果查詢的條件不是主鍵怎麼辦呢,這個時候就需要輔助索引了。

InnoDB——聚簇索引

聚簇索引的主索引的葉子結點儲存的是鍵值對應的資料本身,輔助索引的葉子結點儲存的是鍵值對應的資料的主鍵鍵值。因此主鍵的值長度越小越好,型別越簡單越好。

聚簇索引的資料和主鍵索引儲存在一起。

聚簇索引的資料是根據主鍵的順序儲存。因此適合按主鍵索引的區間查詢,可以有更少的磁碟I/O,加快查詢速度。但是也是因為這個原因,聚簇索引的插入順序最好按照主鍵單調的順序插入,否則會頻繁的引起頁分裂,嚴重影響效能。

在InnoDB中,如果只需要查詢索引的列,就儘量不要加入其它的列,這樣會提高查詢效率。

使用主索引的時候,更適合使用聚簇索引,因為聚簇索引只需要查詢一次,而非聚簇索引在查到資料的地址後,還要進行一次I/O查詢資料。

因為聚簇輔助索引儲存的是主鍵的鍵值,因此可以在資料行移動或者頁分裂的時候降低委會成本,因為這時不用維護輔助索引。但是輔助索引會佔用更多的空間。

聚簇索引在插入新資料的時候比非聚簇索引慢很多,因為插入新資料時需要減壓主鍵是否重複,這需要遍歷主索引的所有葉節點,而非聚簇索引的葉節點儲存的是資料地址,佔用空間少,因此分佈集中,查詢的時候I/O更少,但聚簇索引的主索引中儲存的是資料本身,資料佔用空間大,分佈範圍更大,可能佔用好多的扇區,因此需要更多次I/O才能遍歷完畢。

下圖可以形象的說明聚簇索引和非聚簇索引的區別

image

五、索引的使用策略

什麼時候要使用索引?

主鍵自動建立唯一索引;

經常作為查詢條件在WHERE或者ORDER BY 語句中出現的列要建立索引;

作為排序的列要建立索引;

查詢中與其他表關聯的欄位,外來鍵關係建立索引

高併發條件下傾向組合索引;

什麼時候不要使用索引?

經常增刪改的列不要建立索引;

有大量重複的列不建立索引;

表記錄太少不要建立索引;

在組合索引中不能有列的值為NULL,如果有,那麼這一列對組合索引就是無效的;

在一個SELECT語句中,索引只能使用一次,如果在WHERE中使用了,那麼在ORDER BY中就不要用了;

LIKE操作中,'%aaa%'不會使用索引,也就是索引會失效,但是‘aaa%’可以使用索引;

在索引的列上使用表示式或者函式會使索引失效,例如:select * from users where YEAR(adddate)<2018,將在每個行上進行運算,這將導致索引失效而進行全表掃描,因此我們可以改成:select * from users where adddate<’2018-12-24′。

在查詢條件中使用正則表示式時,只有在搜尋模板的第一個字元不是萬用字元的情況下才能使用索引。

在查詢條件中使用<>會導致索引失效。

在查詢條件中使用IS NULL會導致索引失效。

在查詢條件中使用OR連線多個條件會導致索引失效,這時應該改為兩次查詢,然後用UNION ALL連線起來。

儘量不要包括多列排序,如果一定要,最好為這佇列構建組合索引;

只有當資料庫裡已經有了足夠多的測試資料時,它的效能測試結果才有實際參考價值。如果在測試資料庫裡只有幾百條資料記錄,它們往往在執行完第一條查詢命令之後就被全部載入到記憶體裡,這將使後續的查詢命令都執行得非常快--不管有沒有使用索引。只有當資料庫裡的記錄超過了1000條、資料總量也超過了MySQL伺服器上的記憶體總量時,資料庫的效能測試結果才有意義。

六、索引的優化

1、最左字首

索引的最左字首和和B+Tree中的“最左字首原理”有關,舉例來說就是如果設定了組合索引<col1,col2,col3>那麼以下3中情況可以使用索引:col1,<col1,col2>,<col1,col2,col3>,其它的列,比如<col2,col3>,<col1,col3>,col2,col3等等都是不能使用索引的。

根據最左字首原則,我們一般把排序分組頻率最高的列放在最左邊,以此類推。

2、帶索引的模糊查詢優化

在上面已經提到,使用LIKE進行模糊查詢的時候,'%aaa%'不會使用索引,也就是索引會失效。如果是這種情況,只能使用全文索引來進行優化(上文有講到)。

為檢索的條件構建全文索引,然後使用

SELECT * FROM tablename MATCH(index_colum) ANGAINST(‘word’);
複製程式碼

事務介紹

首先,什麼是事務?事務就是一段sql 語句的批處理,但是這個批處理是一個atom(原子),不可分割,要麼都執行,要麼回滾(rollback)都不執行。

MySQL 事務主要用於處理操作量大,複雜度高的資料。比如說,在人員管理系統中,你刪除一個人員,你即需要刪除人員的基本資料,也要刪除和該人員相關的資訊,如信箱,文章等等,這樣,這些資料庫操作語句就構成一個事務!

  • 在 MySQL 中只有使用了 Innodb 資料庫引擎的資料庫或表才支援事務。

  • 事務處理可以用來維護資料庫的完整性,保證成批的 SQL 語句要麼全部執行,要麼全部不執行。

  • 事務用來管理 insert,update,delete 語句

一般來說,事務是必須滿足4個條件(ACID): Atomicity(原子性)、Consistency(穩定性)、Isolation(隔離性)、Durability(可靠性)

  • 1、事務的原子性:一組事務,要麼成功;要麼撤回。

  • 2、穩定性 :有非法資料(外來鍵約束之類),事務撤回。

  • 3、隔離性:事務獨立執行。一個事務處理後的結果,影響了其他事務,那麼其他事務會撤回。事務的100%隔離,需要犧牲速度。

  • 4、可靠性:軟、硬體崩潰後,InnoDB資料表驅動會利用日誌檔案重構修改。可靠性和高速度不可兼得, innodb_flush_log_at_trx_commit 選項 決定什麼時候吧事務儲存到日誌裡。

image

事務併發並不進行事務隔離造成的髒讀、幻讀、不可重複讀

  • 髒讀:事務A讀到未提交事務B修改的資料,如果此時事務B中途執行失敗回滾,那麼此時事務A讀取到的就是髒資料。比如事務A對money進行修改,此時事務B讀取到事務A的更新結果,但是如果後面事務A回滾,那麼事務B讀取到的就是髒資料了。

  • 不可重複讀:同一個事務中,對同一份資料讀取的結果不一致。事務A在事務B對資料更新前進行讀取,然後事務B更新提交,事務A再次讀取,這時候兩次讀取的資料不同。

  • 幻讀:(同一個事務中,同一個查詢多次返回的結果不一樣。事務B查詢表的記錄數,然後事務A對錶插入一條記錄,接著事務B再次查詢發現記錄數不同。注意這個解釋是不正確,網路上有很多這樣的解釋,包括我認為比較權威的專家,但是經過實驗發現並不正確。所以這是需要注意的)。可以做這樣一個實驗,事務A查詢記錄數,事務B插入一條記錄(主鍵值為6),提交,然後事務A查詢記錄數,發現記錄數沒有改變,但是此時插入一條主鍵值為6的記錄發現衝突了,感覺像出現了幻覺。

區別

1、髒讀和不可重複讀:髒讀是事務讀取了還未提交事務的更新資料。不可重複讀是同一個事務中,幾次讀取的資料不同。

2、不可重複讀和幻讀的區別:都是在同一個事務中,前者是幾次讀取資料不同,後者是幾次讀取資料整體不同。

隔離級別

image

image

  • 隔離級別改變影響鎖的週期

  • mysql支援上面4種隔離級別,預設為可重複讀

image

image

MySQL有三種鎖的級別:頁級、表級、行級。

  MyISAM和MEMORY儲存引擎採用的是表級鎖(table-level locking);

  BDB儲存引擎採用的是頁面鎖(page-level locking),但也支援表級鎖;

  InnoDB儲存引擎既支援行級鎖(row-level locking),也支援表級鎖,但預設情況下是

採用行級鎖。

MySQL這3種鎖的特性可大致歸納如下: 1、表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低。表級鎖讓多執行緒可以同時從資料表中讀取資料,但是如果另一個執行緒想要寫資料的話,就必須要先取得排他訪問(預設加排他表鎖);(共享讀鎖(Table Read Lock)更新資料時,必須要等到更新完成了,其他執行緒才能訪問(讀)這個表。(獨佔寫鎖(Table Write Lock))

2、行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。

3、頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般。

原則上資料表有一個讀鎖時,其它程序無法對此表進行更新操作,但在一定條件下,MyISAM表也支援查詢和插入操作的併發進行。

一般MyISAM引擎的表也支援查詢和插入操作的併發進行(原則上資料表有一個讀鎖時,其它程序無法對此表進行更新操作)

MyISAM引擎有一個系統變數concurrent_insert,專門用以控制其併發插入的行為,其值分別可以為0、1或2:

a、concurrent_insert為0,不允許併發插入。 &emsp;&emsp;&emsp;&emsp;
b、concurrent_insert為1,如果MyISAM表中沒有空洞(即表的中間沒有被刪除的行),MyISAM允許在一個程序讀表的同時,另一個程序從表尾插入記錄。這也是MySQL的預設設定。 &emsp;&emsp;&emsp;&emsp;
c、concurrent_insert為2,無論MyISAM表中有沒有空洞,都允許在表尾併發插入記錄。
複製程式碼

如果有讀寫請求同時進行的話,MYSQL將會優先執行寫操作。這樣MyISAM表在進行大量的更新操作時(特別是更新的欄位中存在索引的情況下),會造成查詢操作很難獲得讀鎖,從而導致查詢阻塞。

我們還可以調整MyISAM讀寫的優先級別:

&emsp;&emsp;a、通過指定啟動引數low-priority-updates,使MyISAM引擎預設給予讀請求以優先的權利。
&emsp;&emsp;b、通過執行命令SET LOW_PRIORITY_UPDATES=1,使該連線發出的更新請求優先順序降低。
&emsp;&emsp;c、通過指定INSERT、UPDATE、DELETE語句的LOW_PRIORITY屬性,降低該語句的優先順序。
複製程式碼

MyISAM使用的是 flock 類的函式,直接就是對整個檔案進行鎖定(叫做檔案鎖定),MyISAM的資料表是按照單個檔案儲存的,可以針對單個表文件進行鎖定;

InnoDB使用的是 fcntl 類的函式,可以對檔案中區域性資料進行鎖定(叫做行鎖定),InnoDB是一整個檔案,把索引、資料、結構全部儲存在 ibdata 檔案裡,所以必須用行鎖定。

事物控制語句:

BEGIN或START TRANSACTION;顯式地開啟一個事務;     
COMMIT;也可以使用COMMIT WORK,不過二者是等價的。
COMMIT會提交事務,並使已對資料庫進行的所有修改稱為永久性的;      
ROLLBACK;有可以使用ROLLBACK WORK,不過二者是等價的。回滾會結束使用者的事務,並撤銷正在進行的所有未提交的修改;      
SAVEPOINT identifier;SAVEPOINT允許在事務中建立一個儲存點,一個事務中可以有多個SAVEPOINT;     
RELEASE SAVEPOINT identifier;刪除一個事務的儲存點,當沒有指定的儲存點時,執行該語句會丟擲一個異常;     
ROLLBACK TO identifier;把事務回滾到標記點;     
SET TRANSACTION;用來設定事務的隔離級別。
InnoDB儲存引擎提供事務的隔離級別有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ和SERIALIZABLE。
複製程式碼

MYSQL 事務處理主要有兩種方法:

1、用 BEGIN, ROLLBACK, COMMIT來實現

BEGIN 開始一個事務     
ROLLBACK 事務回滾    
COMMIT 事務確認
複製程式碼

2、直接用 SET 來改變 My

SQL 的自動提交模式:

SET AUTOCOMMIT=0 禁止自動提交     
SET AUTOCOMMIT=1 開啟自動提交
複製程式碼

注意點

1、如果事務中sql正確執行,後面沒有commit,結果是不會更新到資料庫的,所以需要手動新增commit。

2、如果事務中部分sql語句出現錯誤,那麼錯誤語句後面不會執行。而我們可能會認為正確操作會回滾撤銷,但是實際上並沒有撤銷正確的操作,此時如果再無錯情況下進行一次commit,之前的正確操作會生效,資料庫會進行更新。