MySQL45讲学习笔记(二)
事務隔離
事務的四個特性ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔離性、持久性)
原子性:都成功或都失敗
一致性:一個事務在執行之前和執行之后,數據庫都必須處以一致性狀態。比如:如果從A賬戶轉賬到B賬戶,不可能因為A賬戶 扣了錢,而B賬戶沒有加錢
隔離性:并發環境中,并發的事務是互相隔離的,一個事務的執行不能被其它事務干擾。
持久性:事務一旦提交后,數據庫中的數據必須被永久的保存下來。即使服務器系統崩潰或服務器宕機等故障。只要數據庫重新啟動,那么一定能夠將其恢復到事務成功結束后的狀態
隔離得越嚴實,效率就會越低。
SQL 標準的事務隔離級別包括:讀未提交(read uncommitted)、讀提交(read committed)、可重復讀(repeatable read)和串行化(serializable )。
假設兩個事務A和B同時執行,都在更改一個變量P
- 讀未提交:A更改P,并在A提交事務之前B查詢到的P是已經被改了的了
- 讀提交:A更改P,在A提交事務之前B查到的P沒變,在A提交事務之后B查到的P變了
- 可重復讀:隨便A怎么更改P,在B開始事務的時候B查到的P和結束事務的時候B查到的P一直是一樣的(B的事務期間B沒有改P)
- 串行化:即對P加鎖,一個事務沒執行完,另一個就不能操作P
實現原理:
數據庫里面會創建一個視圖,訪問的時候以視圖的邏輯結果為準。
- 讀未提交沒有視圖概念
- 讀提交的視圖是在每個 SQL 語句開始執行的時候創建
- 可重復讀的視圖在事務啟動時創建的,整個事務存在期間都用這個視圖,此時的視圖可以認為是靜態的,不受其他事務更新的影響。
- 串行化直接用加鎖的方式來避免并行訪問。
提醒:Oracle 數據庫的默認隔離級別其實就是“讀提交”,因此對于一些從 Oracle 遷移到 MySQL的應用,為保證數據庫隔離級別的一致,你一定要記得將 MySQL 的隔離級別設置為“讀提交”。 配置的方式是,將啟動參數transaction-isolation 的值設置成 READ-COMMITTED。你可以用 show variables來查看當前的值。
事務隔離的實現
在 MySQL 中,實際上每條記錄在更新的時候都會同時記錄一條回滾操作。記錄上的最新值,通過回滾操作,都可以得到前一個狀態的值。
不同時刻啟動的事務會有不同的 read-view,同一條記錄在系統中可以存在多個版本,就是數據庫的多版本并發控制(MVCC)。對。
當系統里沒有比這個回滾日志更早的 read-view 的時候,回滾日志會被刪除。所以盡量不要使用長事務,不然回滾段可能會占用大量存儲空間,而且長事務還占用鎖資源,也可能拖垮整個庫。
事務的啟動方式
例如:
mysql中查看當前自動提交狀態的命令為:show VARIABLES like 'autocommit';
建議總是使用 set autocommit=1, 并通過顯式語句的方式來啟動事務。
如果想減少語句的交互次數,推薦第二種,第二種會比第一種少一個begin,并建議使用commit work and chain語法。
如果執行 commit work and chain而不是commit,則是提交事務并自動啟動下一個事務,這樣也省去了再次執行 begin 語句的開銷。同時帶來的好處是從程序開發的角度明確地知道每個語句是否處于事務中。
可以在 information_schema 庫的 innodb_trx 這個表中查詢長事務
下面這個是查找持續時間超過 60s 的事務。
課后問題:
如何避免長事務對業務的影響?
答:
1.1 確認是否使用了 set autocommit=0。
1.2 確認是否有不必要的只讀事務,即框住了幾個select
1.3 業務連接數據庫的時候,根據業務本身的預估,通過 SET MAX_EXECUTION_TIME 命令,來控制每個語句執行的最長時間,避免單個語句意外執行太長時間。
2.1 監控 information_schema.Innodb_trx 表,設置長事務閾值,超過就報警 / 或者 kill;
2.2 Percona 的 pt-kill 這個工具不錯,推薦使用;
2.3 在業務功能測試階段要求輸出所有的 general_log,分析日志行為提前發現問題;
2.4 如果使用的是 MySQL 5.6 或者更新版本,把 innodb_undo_tablespaces 設置成 2(或更大的值)。如果真的出現大事務導致回滾段過大,這樣設置后清理起來更方便。
索引
索引的常見模型
- 哈希表
一種以鍵 - 值(key-value)存儲數據的結構
好處是增加新的數據時速度會很快。但缺點是,因為不是有序的,所以哈希索引做區間查詢的速度是很慢的。所以,哈希表這種結構適用于只有等值查詢的場景 - 有序數組
有序數組在等值查詢和范圍查詢場景中的性能就都非常優秀,配合二分法,查找只要O(logN)O(logN)O(logN),但是插入數據時需要進行O(N)O(N)O(N)的數據移動,所以有序數組索引只適用于靜態存儲引擎,比如存儲去年的統計信息 - 平衡二叉樹
為了維持O(logN)O(logN)O(logN)的查詢復雜度,就不說最基本的二叉搜索樹了。平衡樹的更新也是O(logN)O(logN)O(logN) - N叉樹
實際上大多數的數據庫存儲卻并不使用二叉樹。其原因是,索引不止存在內存中,還要寫到磁盤上。因為磁盤IO次數與樹的高度成正比,為了減少磁盤IO次數,我們將子結點數量增多,以此降低樹的高度 - 等等
InnoDB的索引模型
在 InnoDB 中,表都是根據主鍵順序以索引的形式存放的,這種存儲方式的表稱為索引組織表。
InnoDB使用了B+樹,每一個索引在 InnoDB 里面對應一棵 B+ 樹。
舉個索引存儲的例子
一張表,表有主鍵索引ID和非主鍵索引k和數據R,表中 R1~R5 的 (ID,k) 值分別為 (100,1)、(200,2)、(300,3)、(500,5) 和 (600,6),兩棵樹的示例示意圖如下。
即左樹為主鍵ID的索引樹,右樹為非主鍵索引k的索引樹
根據葉子節點的內容,索引類型分為主鍵索引和非主鍵索引。
主鍵索引的葉子節點存的是整行數據。在 InnoDB 里,主鍵索引也被稱為聚簇索引(clustered index)。
非主鍵索引的葉子節點內容是主鍵的值。在 InnoDB 里,非主鍵索引也被稱為二級索引(secondary index)。
如果查詢的條件語句是where ID=500 ,那就會搜索ID這棵樹
如果條件語句是where k=5 那就會先搜索k這棵樹,再拿著搜到的主鍵ID去ID那棵樹里搜。這個過程也稱為回表。
也就是說,基于非主鍵索引的查詢需要多掃描一棵索引樹。因此,我們在應用中應該盡量使用主鍵查詢。
(下面這張圖不來自上面的例子,隨便找的)
索引維護
B+樹的維護有點麻煩,比如下圖要插入一個400,那就要挪動500和600
而更糟的情況是,如果 R5 所在的數據頁已經滿了,根據 B+ 樹的算法,這時候需要申請一個新的數據頁,然后挪動部分數據過去。這個過程稱為頁分裂。而且頁分裂還影響數據頁的利用率。原本放在一個頁的數據,現在分到兩個頁中,整體空間利用率降低大約 50%。
當然為了提高利用率,也會進行合并
自增主鍵的使用
一些建表規范里面要求建表語句里一定要有自增主鍵。
在建表語句中一般是這么定義的: NOT NULL PRIMARY KEY AUTO_INCREMENT。插入新記錄的時候可以不指定 ID 的值,系統會獲取當前 ID 最大值加 1 作為下一條記錄的 ID 值。
對比前面那個插入操作,遞增的插入就只會在結點數組的后面追加,而不會出現后面的數據全部挪動的情況,也不會觸發葉子節點的分裂,就很好。
那我們為什么不選用業務邏輯字段作為主鍵呢?
答:業務邏輯不容易保證有序插入
我們為什么不選用如身份證這種也具有唯一性的字段作為主鍵呢?
答:主鍵長度越小,普通索引的葉子節點就越小,普通索引占用的空間也就越小。
所以,從性能和存儲空間方面考量,自增主鍵往往是更合理的選擇。
有沒有什么場景適合用業務字段直接做主鍵的呢?
答:K-V場景,即只有一個索引且該索引必須是唯一索引,這種時候直接設K為主鍵,避免每次查詢需要搜索兩棵樹。
覆蓋索引
對于一般的區間查詢,比如前面那個例子,對于表(ID,k,R),有主鍵ID和二級索引k,我們要select * from T where k between 3 and 5,那么會先在k的索引樹中查3,再拿對應的ID去查ID的索引樹中查R,再在k的索引樹中查4,再去ID樹查R,再在k索引樹查5,沒有了就停止了,返回數據集。
我們發現上面就回表了兩次,顯然是低效的
我們再舉個例子,表和索引還是前面那樣,但是查詢語句是select ID from T where k between 3 and 5,我們發現根據k查到的就是ID,直接就拿到數據了,不需要回表什么的,減少了樹的搜索次數,顯著提升了查詢性能,這就是覆覆蓋索引
覆蓋索引就是select的數據列只用從索引中就能夠取得,不必從數據表中讀取,換句話說查詢列要被所使用的索引覆蓋
一般針對聯合索引,如果篩選的字段不在聯合索引,那么索引會失效
顯然這是一種空間換時間的操作,對于高頻請求,我們就可以建立這種聯合索引
最左前綴原則
為了直觀地說明這個概念,我們用(name,age)這個聯合索引來分析。
可以看到,索引項是按照索引定義里面出現的字段順序排序的。
當你的邏輯需求是查到所有名字是“張三”的人時,可以快速定位到 ID4,然后向后遍歷得到所有需要的結果。
但是如果我們的查詢條件語句是where name like ‘張 %’時,其實也可以用到這個索引,會先查找到ID3,然后向后遍歷
不只是索引的全部定義,只要滿足最左前綴,就可以利用索引來加速檢索。這個最左前綴可以是聯合索引的最左 N 個字段,也可以是字符串索引的最左 M 個字符。
所以查詢%com和com%的速度就不一樣了,所以解決辦法就是倒著插入域名數據moc.udiab.www,這樣查詢的時候就是’com%’,這樣就可以使用索引了,這也是一個在使用數據庫時的小技巧。
因為可以支持最左前綴,所以當已經有了 (a,b) 這個聯合索引后,一般就不需要單獨在 a 上建立索引了。
因此,設置聯合索引內字段順序的第一原則是:如果通過調整順序,可以少維護一個索引,那么這個順序往往就是需要優先考慮采用的。
所以如果有這么一個需求:我們要為高頻請求創建 (身份證號,姓名)這個聯合索引,并用這個索引支持“根據身份證號查詢地址”,我們此時根據最左前綴原則,可以直接用高頻請求的(身份證號,姓名)聯合索引來充當(身份證號)索引的作用。
但是如果有聯合索引(a,b),我們現在要加快以b為條件的查詢語句速度,我們就不得不維護(b)這個索引了,所以我們可以發現我們可以讓字段小一點的字段作為b這個靠右的索引,這樣即便多維護(b)這個索引,也比維護(a)在空間上更優
索引下推
結合圖講起來會好一點
select * from tuser where name like '張 %' and age=10 and ismale=1;-
無索引下推執行流程(MySQL 5.6 之前)
先根據模糊查詢查到ID3,然后只能一個一個遍歷然后回表
-
索引下推執行流程(MySQL 5.6 開始)、
還是查到ID3,但是在聯合索引這邊就利用了age=10 這個條件過濾掉了ID3和ID6這兩個age對不上的,只會回表兩次
總結
以上是生活随笔為你收集整理的MySQL45讲学习笔记(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VPX显示计算机学习资料第711篇:飞腾
- 下一篇: Groovy的元编程