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