《MySQL实战45讲》基础理论篇 1-8讲 学习笔记
圖片來自于極客時間,如有版權問題,請聯系我刪除。
對于索引一些比較了解的,記錄的比較少,感興趣的可以看我其他MySQL的文章
01 | 基礎架構:一條SQL查詢語句是如何執行的?
1.連接器
如果用戶名密碼認證通過,連接器會到權限表里面查出你擁有的權限。之后,這個連接里面的權限判斷邏輯,都將依賴于此時讀到的權限。
這就意味著,一個用戶成功建立連接后,即使你用管理員賬號對這個用戶的權限做了修改,也不會影響已經存在連接的權限。修改完成后,只有再新建的連接才會使用新的權限設置
建立連接的過程通常是比較復雜的,所以在使用中要盡量減少建立連接的動作,也就是盡量使用長連接。
但是全部使用長連接后,有些時候 MySQL 占用內存漲得特別快,這是因為 MySQL 在執行過程中臨時使用的內存是管理在連接對象里面的。這些資源會在連接斷開的時候才釋放。所以如果長連接累積下來,可能導致內存占用太大,被系統強行殺掉(OOM),從現象看就是 MySQL 異常重啟了。
怎么解決這個問題呢?你可以考慮以下兩種方案。
1.定期斷開長連接。使用一段時間,或者程序里面判斷執行過一個占用內存的大查詢后,斷開連接,之后要查詢再重連。
2.如果你用的是 MySQL 5.7 或更新版本,可以在每次執行一個比較大的操作后,通過執行 mysql_reset_connection 來重新初始化連接資源。這個過程不需要重連和重新做權限驗證,但是會將連接恢復到剛剛創建完時的狀態
2.查詢緩存
查詢緩存的失效非常頻繁,不建議使用
MySQL 提供“按需使用”的方式。你可以將參數 query_cache_type 設置成 DEMAND,這樣對于默認的 SQL 語句都不使用查詢緩存。而對于你確定要使用查詢緩存的語句,可以用 SQL_CACHE 顯式指定,像下面這個語句一樣
mysql> select SQL_CACHE * from T where ID=10;
需要注意的是,MySQL 8.0 版本直接將查詢緩存的整塊功能刪掉了,也就是說 8.0 開始徹底沒有這個功能了
3.分析器
詞法分析 語法分析(是否滿足MySQL語法)
4.優化器
優化器是在表里面有多個索引的時候,決定使用哪個索引;或者在一個語句有多表關聯(join)的時候,決定各個表的連接順序
優化器階段完成后,這個語句的執行方案就確定下來了
5.執行器
開始執行的時候,要先判斷一下你對這個表 T 有沒有執行查詢的權限,如果沒有,就會返回沒有權限的錯誤, (在工程實現上,如果命中查詢緩存,會在查詢緩存返回結果的時候做權限驗證。查詢也會在優化器之前調用 precheck 驗證權限)
mysql> select * from T where ID=10;
ERROR 1142 (42000): SELECT command denied to user ‘b’@‘localhost’ for table ‘T’
如果有權限,就打開表繼續執行。打開表的時候,執行器就會根據表的引擎定義,去使用這個引擎提供的接口。
比如我們這個例子中的表 T 中,ID 字段沒有索引,那么執行器的執行流程是這樣的:
你會在數據庫的慢查詢日志中看到一個 rows_examined 的字段,表示這個語句執行過程中掃描了多少行。
這個值就是在執行器每次調用引擎獲取數據行的時候累加的。在有些場景下,執行器調用一次,在引擎內部則掃描了多行,因此引擎掃描行數跟 rows_examined 并不是完全相同的
02 | 日志系統:一條SQL更新語句是如何執行的?
redo log
為了提高更新效率,MySQL使用了 WAL 技術,WAL 的全稱是 Write-Ahead Logging,它的關鍵點就是先寫日志,再寫磁盤
具體來說,當有一條記錄需要更新的時候,InnoDB 引擎就會先把記錄寫到 redo log(粉板)里面,并更新內存,這個時候更新就算完成了。同時,InnoDB 引擎會在適當的時候,將這個操作記錄更新到磁盤里面,而這個更新往往是在系統比較空閑的時候做。
InnoDB 的 redo log 是固定大小的,比如可以配置為一組 4 個文件,每個文件的大小是 1GB,那么這塊“粉板”總共就可以記錄 4GB 的操作。從頭開始寫,寫到末尾就又回到開頭循環寫,如下面這個圖所示。
write pos 是當前記錄的位置,一邊寫一邊后移,寫到第 3 號文件末尾后就回到 0 號文件開頭。checkpoint 是當前要擦除的位置,也是往后推移并且循環的,擦除記錄前要把記錄更新到數據文件。
write pos 和 checkpoint 之間的是“粉板”上還空著的部分,可以用來記錄新的操作。如果 write pos 追上 checkpoint,表示“粉板”滿了,這時候不能再執行新的更新,得停下來先擦掉一些記錄,把 checkpoint 推進一下。
有了 redo log,InnoDB 就可以保證即使數據庫發生異常重啟,之前提交的記錄都不會丟失,這個能力稱為 crash-safe。
binlog
上面我們聊到的redo log 是 InnoDB 引擎特有的日志,而 Server 層也有自己的日志,稱為 binlog(歸檔日志)。
為什么會有兩份日志呢?因為最開始 MySQL 里并沒有 InnoDB 引擎。MySQL 自帶的引擎是 MyISAM,但是 MyISAM 沒有 crash-safe 的能力,binlog 日志只能用于歸檔。而 InnoDB 是另一個公司以插件形式引入 MySQL 的,既然只依靠 binlog 是沒有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系統——也就是 redo log 來實現 crash-safe 能力。
這兩種日志有以下三點不同。
Redo log不是記錄數據頁“更新之后的狀態”,而是記錄這個頁 “做了什么改動”
Binlog有兩種模式,statement 格式的話是記sql語句, row格式會記錄行的內容,記兩條,更新前和更新后都有。
兩階段提交
怎樣讓數據庫恢復到半個月內任意一秒的狀態?
binlog 會記錄所有的邏輯操作,采用“追加寫”的形式。如果你的 DBA 承諾說半個月內可以恢復,那么備份系統中一定會保存最近半個月的所有 binlog,同時系統會定期做整庫備份(一天一備/一周一備)
當需要恢復到指定的某一秒時,比如某天下午兩點發現中午十二點有一次誤刪表,需要找回數據,那你可以這么做:
首先,找到最近的一次全量備份,如果你運氣好,可能就是昨天晚上的一個備份,從這個備份恢復到臨時庫;
然后,從備份的時間點開始,將備份的 binlog 依次取出來,重放到中午誤刪表之前的那個時刻。
這樣你的臨時庫就跟誤刪之前的線上庫一樣了,然后你可以把表數據從臨時庫取出來,按需要恢復到線上庫去
redo log 用于保證 crash-safe 能力。innodb_flush_log_at_trx_commit 這個參數設置成 1 的時候,表示每次事務的 redo log 都直接持久化到磁盤。
這個參數我建議你設置成 1,這樣可以保證 MySQL 異常重啟之后數據不丟失。sync_binlog 這個參數設置成 1 的時候,表示每次事務的 binlog 都持久化到磁盤。這個參數我也建議你設置成 1,這樣可以保證 MySQL 異常重啟之后 binlog 不丟失。
MySQL 日志系統密切相關的“兩階段提交”。兩階段提交是跨系統維持數據邏輯一致性時常用的一個方案,即使你不做數據庫內核開發,日常開發中也有可能會用到。
問題:為什么binlog不能用于 crash-safe?
歷史上的原因:
這個是一開始就這么設計的,所以不能只依賴binlog。
操作上的原因是:binlog是可以關的,你如果有權限,可以set sql_log_bin=0關掉本線程的binlog日志。 所以只依賴binlog來恢復就靠不住。
03 | 事務隔離:為什么你改了我還看不見?
數據庫InnoDB不同隔離級別:
在實現上,數據庫里面會創建一個視圖,訪問的時候以視圖的邏輯結果為準。
在“可重復讀”隔離級別下,這個視圖是在事務啟動時創建的,整個事務存在期間都用這個視圖。
在“讀提交”隔離級別下,這個視圖是在每個 SQL 語句開始執行的時候創建的。這里需要注意的是,
“讀未提交”隔離級別下直接返回記錄上的最新值,沒有視圖概念;
而“串行化”隔離級別下直接用加鎖的方式來避免并行訪問。
你可能會問那什么時候需要“可重復讀”的場景呢?
我們來看一個數據校對邏輯的案例。假設你在管理一個個人銀行賬戶表。一個表存了賬戶余額,一個表存了賬單明細。到了月底你要做數據校對,也就是判斷上個月的余額和當前余額的差額,是否與本月的賬單明細一致。
你一定希望在校對過程中,即使有用戶發生了一筆新的交易,也不影響你的校對結果。這時候使用“可重復讀”隔離級別就很方便。事務啟動時的視圖可以認為是靜態的,不受其他事務更新的影響。
我們可以看到在不同的隔離級別下,數據庫行為是有所不同的。Oracle 數據庫的默認隔離級別其實就是“讀提交”,因此對于一些從 Oracle 遷移到 MySQL 的應用,為保證數據庫隔離級別的一致,你一定要記得將 MySQL 的隔離級別設置為“讀提交”。(transaction_isolation)
在 MySQL 中,實際上每條記錄在更新的時候都會同時記錄一條回滾操作。記錄上的最新值,通過回滾操作,都可以得到前一個狀態的值。
假設一個值從 1 被按順序改成了 2、3、4,在回滾日志里面就會有類似下面的記錄。
回滾日志什么時候刪除呢?答案是,在不需要的時候才刪除。也就是說,系統會判斷,當沒有事務再需要用到這些回滾日志時,回滾日志會被刪除。
為什么建議你盡量不要使用長事務。
長事務意味著系統里面會存在很老的事務視圖。由于這些事務隨時可能訪問數據庫里面的任何數據,所以這個事務提交之前,數據庫里面它可能用到的回滾記錄都必須保留,這就會導致大量占用存儲空間。
在 MySQL 5.5 及以前的版本,回滾日志是跟數據字典一起放在 ibdata 文件里的,即使長事務最終提交,回滾段被清理,文件也不會變小。我見過數據只有 20GB,而回滾段有 200GB 的庫。最終只好為了清理回滾段,重建整個庫。除了對回滾段的影響,長事務還占用鎖資源,也可能拖垮整個庫
read-viewA事務未提交的情形下,不能將后面的回滾段刪除,哪怕事務B和C已經提交
有些客戶端連接框架會默認連接成功后先執行一個 set autocommit=0 的命令。這就導致接下來的查詢都在事務中,如果是長連接,就導致了意外的長事務。
因此,我會建議你總是使用 set autocommit=1, 通過顯式語句的方式來啟動事務。
但是有的開發同學會糾結“多一次交互”的問題。對于一個需要頻繁使用事務的業務。如果你也有這個顧慮,建議你使用 commit work and chain 語法。在 autocommit 為 1 的情況下,用 begin 顯式啟動的事務,如果執行 commit 則提交事務。如果執行 commit work and chain,則是提交事務并自動啟動下一個事務,這樣也省去了再次執行 begin 語句的開銷。同時帶來的好處是從程序開發的角度明確地知道每個語句是否處于事務中。
問題解答:
一天一備跟一周一備的對比:
好處是“最長恢復時間”更短。在一天一備的模式里,最壞情況下需要應用一天的 binlog。比如,你每天 0 點做一次全量備份,而要恢復出一個到昨天晚上 23 點的備份。一周一備最壞情況就要應用一周的 binlog 了。
系統的對應指標就是 @尼古拉斯·趙四 @慕塔 提到的 RTO(恢復目標時間)。當然這個是有成本的,因為更頻繁全量備份需要消耗更多存儲空間,所以這個 RTO 是成本換來的,就需要你根據業務重要性來評估了
04 | 深入淺出索引(上)05 | 深入淺出索引(下)
哈希表這種結構適用于只有等值查詢的場景,比如 Memcached 及其他一些 NoSQL 引擎。
有序數組在等值查詢和范圍查詢場景中的性能就都非常優秀
但是有序數組索引只適用于靜態存儲引擎,比如你要保存的是 2017 年某個城市的所有人口信息,這類不會再修改的數據。
二叉樹是搜索效率最高的,但是實際上大多數的數據庫存儲卻并不使用二叉樹。其原因是,索引不止存在內存中,還要寫到磁盤上。你可以想象一下一棵 100 萬節點的平衡二叉樹,樹高 20。一次查詢可能需要訪問 20 個數據塊
可能在一些建表規范里面見到過類似的描述,要求建表語句里一定要有自增主鍵。當然事無絕對,我們來分析一下哪些場景下應該使用自增主鍵,而哪些場景下不應該。
自增主鍵是指自增列上定義的主鍵,在建表語句中一般是這么定義的: NOT NULL PRIMARY KEY AUTO_INCREMENT。
插入新記錄的時候可以不指定 ID 的值,系統會獲取當前 ID 最大值加 1 作為下一條記錄的 ID 值。也就是說,自增主鍵的插入數據模式,正符合了我們前面提到的遞增插入的場景。每次插入一條新記錄,都是追加操作,都不涉及到挪動其他記錄,也不會觸發葉子節點的分裂。
而有業務邏輯的字段做主鍵,則往往不容易保證有序插入,這樣寫數據成本相對較高。除了考慮性能外,我們還可以從存儲空間的角度來看。
假設你的表中確實有一個唯一字段,比如字符串類型的身份證號,那應該用身份證號做主鍵,還是用自增字段做主鍵呢?由于每個非主鍵索引的葉子節點上都是主鍵的值。如果用身份證號做主鍵,那么每個二級索引的葉子節點占用約 20 個字節,而如果用整型做主鍵,則只要 4 個字節,如果是長整型(bigint)則是 8 個字節。
顯然,主鍵長度越小,普通索引的葉子節點就越小,普通索引占用的空間也就越小。
所以,從性能和存儲空間方面考量,自增主鍵往往是更合理的選擇
有沒有什么場景適合用業務字段直接做主鍵的呢?
比如,有些業務的場景需求是這樣的:
只有一個索引;該索引必須是唯一索引。你一定看出來了,這就是典型的 KV 場景。
由于沒有其他索引,所以也就不用考慮其他索引的葉子節點大小的問題。這時候我們就要優先考慮上一段提到的“盡量使用主鍵查詢”原則,直接將這個索引設置為主鍵,可以避免每次查詢需要搜索兩棵樹
如何避免長事務對業務的影響?
首先,從應用開發端來看:
確認是否使用了 set autocommit=0。這個確認工作可以在測試環境中開展,把 MySQL 的 general_log 開起來,然后隨便跑一個業務邏輯,通過 general_log 的日志來確認。一般框架如果會設置這個值,也就會提供參數來控制行為,你的目標就是把它改成 1。
確認是否有不必要的只讀事務。比方說把好幾個 select 語句放到了事務中。這種只讀事務可以去掉。
業務連接數據庫的時候,根據業務本身的預估,通過 SET MAX_EXECUTION_TIME 命令,來控制每個語句執行的最長時間,避免單個語句意外執行太長時間
從數據庫端來看:
監控 information_schema.Innodb_trx 表,設置長事務閾值,超過就報警 / 或者 kill;
Percona 的 pt-kill 這個工具不錯,推薦使用;
在業務功能測試階段要求輸出所有的 general_log,分析日志行為提前發現問題;
如果使用的是 MySQL 5.6 或者更新版本,把 innodb_undo_tablespaces 設置成 2(或更大的值)。如果真的出現大事務導致回滾段過大,這樣設置后清理起來更方便
最左前綴:聯合索引的最左 N 個字段,也可以是字符串索引的最左 M 個字符
覆蓋索引
在一個市民信息表上,是否有必要將身份證號和名字建立聯合索引?
單從索引過濾的使用方面沒有必要,但是使用聯合索引,在根據身份證查詢姓名的時候可以不需要回表–這個是否需要創建冗余索引需要權衡
在建立聯合索引的時候,如何安排索引內的字段順序。
第一原則是,如果通過調整順序,可以少維護一個索引,那么這個順序往往就是需要優先考慮采用的。
如果既有聯合查詢,又有基于 a、b 各自的查詢呢?
查詢條件里面只有 b 的語句,是無法使用 (a,b) 這個聯合索引的,這時候你不得不維護另外一個索引,也就是說你需要同時維護 (a,b)、(b) 這兩個索引。
這時候,我們要考慮的原則就是空間了。比如上面這個市民表的情況,name 字段是比 age 字段大的 ,那我就建議你創建一個(name,age) 的聯合索引和一個 (age) 的單字段索引 (比 (age,name), (name)更節省空間)
通過兩個 alter 語句重建索引 k,以及通過兩個 alter(先drop再add) 語句重建主鍵索引是否合理
重建索引 k 的做法是合理的,可以達到省空間的目的(對于有頁分裂的數據進行重新排序,節省空間)。但是,重建主鍵的過程不合理。不論是刪除主鍵還是創建主鍵,都會將整個表重建。所以連著執行這兩個語句的話,第一個語句就白做了。這兩個語句,你可以用這個語句代替 : alter table T engine=InnoDB
06 | 全局鎖和表鎖 :給表加個字段怎么有這么多阻礙?
全局鎖和表鎖
全局鎖
**全局鎖就是對整個數據庫實例加鎖。**MySQL 提供了一個加全局讀鎖的方法,命令是 Flush tables with read lock (FTWRL)。當你需要讓整個庫處于只讀狀態的時候,可以使用這個命令,之后其他線程的以下語句會被阻塞:數據更新語句(數據的增刪改)、數據定義語句(包括建表、修改表結構等)和更新類事務的提交語句
全局鎖的典型使用場景是,做全庫邏輯備份。也就是把整庫每個表都 select 出來存成文本。
我們在前面講事務隔離的時候,其實是有一個方法能夠拿到一致性視圖的,對吧?是的,就是在可重復讀隔離級別下開啟一個事務
官方自帶的邏輯備份工具是 mysqldump。當 mysqldump 使用參數–single-transaction 的時候,導數據之前就會啟動一個事務,來確保拿到一致性視圖。而由于 MVCC 的支持,這個過程中數據是可以正常更新的
一致性讀是好,但前提是引擎要支持這個隔離級別。比如,對于 MyISAM 這種不支持事務的引擎,如果備份過程中有更新,總是只能取到最新的數據,那么就破壞了備份的一致性。這時,我們就需要使用 FTWRL 命令了
既然要全庫只讀,為什么不使用 set global readonly=true 的方式呢?
確實 readonly 方式也可以讓全庫進入只讀狀態,但我還是會建議你用 FTWRL 方式,主要有兩個原因:
一是,在有些系統中,readonly 的值會被用來做其他邏輯,比如用來判斷一個庫是主庫還是備庫。因此,修改 global 變量的方式影響面更大,不建議使用。
二是,在異常處理機制上有差異。如果執行 FTWRL 命令之后由于客戶端發生異常斷開,那么 MySQL 會自動釋放這個全局鎖,整個庫回到可以正常更新的狀態。而將整個庫設置為 readonly 之后,如果客戶端發生異常,則數據庫就會一直保持 readonly 狀態,這樣會導致整個庫長時間處于不可寫狀態,風險較高。
表級鎖
MySQL 里面表級別的鎖有兩種:一種是表鎖,一種是元數據鎖(meta data lock,MDL)。
表鎖的語法是 lock tables … read/write
lock tables 語法除了會限制別的線程的讀寫外,也限定了本線程接下來的操作對象。
另一類表級的鎖是 MDL(metadata lock)
MDL 不需要顯式使用,在訪問一個表的時候會被自動加上
在 MySQL 5.5 版本中引入了 MDL,當對一個表做增刪改查操作的時候,加 MDL 讀鎖;當要對表做結構變更操作的時候,加 MDL 寫鎖。
雖然 MDL 鎖是系統默認會加的,但卻是你不能忽略的一個機制。比如下面這個例子,我經常看到有人掉到這個坑里:給一個小表加個字段,導致整個庫掛了
sessionC會由于sessionA和sessionB處于阻塞狀態。并且之后所有要在表 t 上新申請 MDL 讀鎖的請求也會被 session C 阻塞(mysql Server端對于sessionC,D會有一個 隊列 來決定誰先執行,所以雖然session C的執行被阻塞了,但是session D的執行也會被session C阻塞)
事務中的 MDL 鎖,在語句執行開始時申請,但是語句結束后并不會馬上釋放,而會等到整個事務提交后再釋放。
如何安全地給小表加字段?
首先我們要解決長事務,事務不提交,就會一直占著 MDL 鎖。
在 MySQL 的 information_schema 庫的 innodb_trx 表中,你可以查到當前執行中的事務。如果你要做 DDL 變更的表剛好有長事務在執行,要考慮先暫停 DDL,或者 kill 掉這個長事務。
但考慮一下這個場景。如果你要變更的表是一個熱點表,雖然數據量不大,但是上面的請求很頻繁,而你不得不加個字段,你該怎么做呢?這時候 kill 可能未必管用,因為新的請求馬上就來了。
比較理想的機制是,在 alter table 語句里面設定等待時間,如果在這個指定的等待時間里面能夠拿到 MDL 寫鎖最好,拿不到也不要阻塞后面的業務語句,先放棄。之后開發人員或者 DBA 再通過重試命令重復這個過程。
MariaDB 已經合并了 AliSQL 的這個功能,所以這兩個開源分支目前都支持 DDL NOWAIT/WAIT n 這個語法。
索引問題:
對于下面的語句
select … from geek where c=N order by a
走ca,cb索引都能定位到滿足c=N主鍵而且主鍵的聚簇索引本身就是按order by a,b排序,無序重新排序。
所以ca可以去掉
select … from geek where c=N order by b 這條sql如果只有 c單個字段的索引,定位記錄可以走索引,但是order by b的順序與主鍵順序不一致,需要額外排序cb索引可以把排序優化
課后問題 : 當備庫用–single-transaction 做邏輯備份的時候,如果從主庫的 binlog 傳來一個 DDL 語句會怎么樣?
Q1:SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Q2:START TRANSACTION WITH CONSISTENT SNAPSHOT;
/* other tables /
Q3:SAVEPOINT sp;
/ 時刻 1 /
Q4:show create table t1;
/ 時刻 2 /
Q5:SELECT * FROM t1;
/ 時刻 3 /
Q6:ROLLBACK TO SAVEPOINT sp;
/ 時刻 4 /
/ other tables */
在備份開始的時候,為了確保 RR(可重復讀)隔離級別,再設置一次 RR 隔離級別 (Q1);
啟動事務,這里用 WITH CONSISTENT SNAPSHOT 確保這個語句執行完就可以得到一個一致性視圖(Q2);
設置一個保存點,這個很重要(Q3);
show create 是為了拿到表結構 (Q4),然后正式導數據 (Q5),
回滾到 SAVEPOINT sp,在這里的作用是釋放 t1 的 MDL 鎖 (Q6)。
當然這部分屬于“超綱”,上文正文里面都沒提到。DDL 從主庫傳過來的時間按照效果不同,我打了四個時刻。題目設定為小表,我們假定到達后,如果開始執行,則很快能夠執行完成
1)如果在 Q4 語句執行之前到達,現象:沒有影響,備份拿到的是 DDL 后的表結構。
2)如果在“時刻 2”到達,則表結構被改過,Q5 執行的時候,報 Table definition has changed, please retry transaction,現象:mysqldump 終止;
3)如果在“時刻 2”和“時刻 3”之間到達,mysqldump 占著 t1 的 MDL 讀鎖,binlog 被阻塞,現象:主從延遲,直到 Q6 執行完成。
4)從“時刻 4”開始,mysqldump 釋放了 MDL 讀鎖,現象:沒有影響,備份拿到的是 DDL 前的表結構。
07 | 行鎖功過:怎么減少行鎖對性能的影響?
在 InnoDB 事務中,行鎖是在需要的時候才加上的,但并不是不需要了就立刻釋放,而是要等到事務結束時才釋放。這個就是兩階段鎖協議
怎么解決由這種熱點行更新導致的性能問題呢?問題的癥結在于,死鎖檢測要耗費大量的 CPU 資源。
一種頭痛醫頭的方法,就是如果你能確保這個業務一定不會出現死鎖,可以臨時把死鎖檢測關掉
另一個思路是控制并發度。
這個并發控制要做在數據庫服務端。如果你有中間件,可以考慮在中間件實現;如果你的團隊有能修改 MySQL 源碼的人,也可以做在 MySQL 里面。基本思路就是,對于相同行的更新,在進入引擎之前排隊。這樣在 InnoDB 內部就不會有大量的死鎖檢測工作了。
如果團隊里暫時沒有數據庫方面的專家,不能實現這樣的方案,能不能從設計上優化這個問題呢?
你可以考慮通過將一行改成邏輯上的多行來減少鎖沖突。還是以影院賬戶為例,可以考慮放在多條記錄上,比如 10 個記錄,影院的賬戶總額等于這 10 個記錄的值的總和。這樣每次要給影院賬戶加金額的時候,隨機選其中一條記錄來加。這樣每次沖突概率變成原來的 1/10,可以減少鎖等待個數,也就減少了死鎖檢測的 CPU 消耗。
減少死鎖的主要方向,就是控制訪問相同資源的并發事務量。
課后問題: 怎么刪除表的前 10000 行。=
在一個連接中循環執行 20 次 delete from T limit 500效率會比較好。確實是這樣的,第二種方式是相對較好的。
第一種方式(即:直接執行 delete from T limit 10000)里面,單個語句占用時間長,鎖的時間也比較長;而且大事務還會導致主從延遲。
第三種方式(即:在 20 個連接中同時執行 delete from T limit 500),會人為造成鎖沖突。
08 | 事務到底是隔離的還是不隔離的?
begin/start transaction 命令并不是一個事務的起點,在執行到它們之后的第一個操作 InnoDB 表的語句,事務才真正啟動。如果你想要馬上啟動一個事務,可以使用 start transaction with consistent snapshot 這個命令。
第一種啟動方式,一致性視圖是在執行第一個快照讀語句時創建的;
第二種啟動方式,一致性視圖是在執行 start transaction with consistent snapshot 時創建的。
在 MySQL 里,有兩個“視圖”的概念:
一個是 view。它是一個用查詢語句定義的虛擬表,在調用的時候執行查詢語句并生成結果。創建視圖的語法是 create view … ,而它的查詢方法與表一樣。
另一個是 InnoDB 在實現 MVCC 時用到的一致性讀視圖,即 consistent read view,用于支持 RC(Read Committed,讀提交)和 RR(Repeatable Read,可重復讀)隔離級別的實現。
它沒有物理結構,作用是事務執行期間用來定義“我能看到什么數據”
我們這里說的是第二種
InnoDB 里面每個事務有一個唯一的事務 ID,叫作 transaction id。它是在事務開始的時候向 InnoDB 的事務系統申請的,是按申請順序嚴格遞增的。
而每行數據也都是有多個版本的。每次事務更新數據的時候,都會生成一個新的數據版本,并且把 transaction id 賦值給這個數據版本的事務 ID,記為 row trx_id。同時,舊的數據版本要保留,并且在新的數據版本中,能夠有信息可以直接拿到它。
也就是說,數據表中的一行記錄,其實可能有多個版本 (row),每個版本有自己的 row trx_id。
圖中的三個虛線箭頭,就是 undo log;而 V1、V2、V3 并不是物理上真實存在的,而是每次需要的時候根據當前版本和 undo log 計算出來的。比如,需要 V2 的時候,就是通過 V4 依次執行 U3、U2 算出來
因此,一個事務只需要在啟動的時候聲明說,“以我啟動的時刻為準,如果一個數據版本是在我啟動之前生成的,就認;如果是我啟動以后才生成的,我就不認,我必須要找到它的上一個版本
在實現上, InnoDB 為每個事務構造了一個數組,用來保存這個事務啟動瞬間,當前正在“活躍”的所有事務 ID。“活躍”指的就是,啟動了但還沒提交
數組里面事務 ID 的最小值記為低水位,當前系統里面已經創建過的事務 ID 的最大值加 1 記為高水位。
這個視圖數組和高水位,就組成了當前事務的一致性視圖(read-view)
當開啟事務時,需要保存活躍事務的數組(A),然后獲取高水位(B)。兩個動作之間(A和B之間)是在事務系統的鎖保護下做的,可以認為是原子性的,不會產生新事務
如果落在黃色部分,那就包括兩種情況
a. 若 row trx_id 在數組中,表示這個版本是由還沒提交的事務生成的,不可見;
b. 若 row trx_id 不在數組中,表示這個版本是已經提交了的事務生成的,可見(比方說開啟事務11后,又開啟了事務12并快速提交,此時當事務11執行SQL生成一致性快照的時候就會出現黃色區域的b情形)
InnoDB 利用了“所有數據都有多個版本”的這個特性,實現了“秒級創建快照”的能力 (這個快照本質上就是上面的高低水位圖,創建非常快速)
當它要去更新數據的時候,就不能再在歷史版本上更新了,否則事務 C 的更新就丟失了。因此,事務 B 此時的 set k=k+1 是在(1,2)的基礎上進行的操作。
所以,這里就用到了這樣一條規則:更新數據都是先讀后寫的,而這個讀,只能讀當前的值,稱為“當前讀”(current read)。
當前讀: 除了 update 語句外,select 語句如果加鎖,也是當前讀(不過注意這個當前讀需要獲取鎖,只有當本事務獲取到對應的鎖資源之后才可以讀取當前最新的數據))
事務的可重復讀的能力是怎么實現的?
可重復讀的核心就是一致性讀(consistent read);而事務更新數據的時候,只能用當前讀。如果當前的記錄的行鎖被其他事務占用的話,就需要進入鎖等待。
總結
以上是生活随笔為你收集整理的《MySQL实战45讲》基础理论篇 1-8讲 学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 单元测试之更强大的powermock
- 下一篇: 《MySQL实战45讲》实践篇 9-15