數據庫并發控制——鎖, Multiversion (version) concurrency control (MCC or MVCC) 多版本并發控制 ,它是數據庫管理系統一種常見的并發控制。
并發控制常用的是鎖,當線程要對一個共享資源進行操作的時候,加鎖是一種非常簡單粗暴的方法(事務開始時給 DQL 加讀鎖,給 DML 加寫鎖),這種鎖是一種 悲觀 的實現方式,也就是說這會給其他事務造成堵塞,從而影響數據庫性能。
其中在數據庫中最常見的就是悲觀鎖和樂觀鎖:
* 悲觀鎖
當一個線程需要對共享資源進行操作的時候,首先對共享資源進行加鎖,當該線程持有該資源的鎖的時候,其他線程對該資源進行操作的時候會被阻塞.
* 樂觀鎖
當一個線程需要對一個共享資源進行操作的時候,不對它進行加鎖,而是在操作完成之后進行判斷。 比如樂觀鎖會通過一個版本號控制,如果操作完成后通過版本號進行判斷在該線程操作過程中是否有其他線程已經對該共享資源進行操作了,如果有則通知操作失敗,如果沒有則操作成功,當然除了版本號還有CAS,如果不了解的可以去學習一下,這里不做過多涉及。
MVCC的兩種讀形式:
* 快照讀
讀取的只是當前事務的可見版本,不用加鎖。而你只要記住 簡單的`select`操作就是快照讀(select \* from table where id = xxx)。
* 當前讀
讀取的是當前版本,比如 特殊的讀操作,更新/插入/刪除操作.
比如:
~~~gfm
select * from table where xxx lock in share mode,
select * from table where xxx for update,
update table set....
insert into table (xxx,xxx) values (xxx,xxx)
delete from table where id = xxx
~~~
MVCC的實現原理:
MVCC 使用了“三個隱藏字段”來實現版本并發控制,MySQL在創建建表的時候 innoDB 創建的真正的三個隱藏列吧。
| RowID | DB\_TRX\_ID | DB\_ROLL\_PTR | id | name | password |
| --- | --- | --- | --- | --- | --- |
| 自動創建的id | 事務id | 回滾指針 | id | name | password |
* RowID:隱藏的自增ID,當建表沒有指定主鍵,InnoDB會使用該RowID創建一個聚簇索引。
* DB\_TRX\_ID:最近修改(更新/刪除/插入)該記錄的事務ID。
* DB\_ROLL\_PTR:回滾指針,指向這條記錄的上一個版本。
其實還有一個刪除的flag字段,用來判斷該行記錄是否已經被刪除。
而 MVCC 使用的是其中的 事務字段,回滾指針字段,是否刪除字段。
我們來看一下現在的表格(isDelete是我自己取的,按照官方說法是在一行開頭的content里面,這里其實位置無所謂,你只要知道有就行了)。
| isDelete | DB\_TRX\_ID | DB\_ROLL\_PTR | id | name | password |
| --- | --- | --- | --- | --- | --- |
| true/false | 事務id | 回滾指針 | id | name | password |
- Golang基礎
- Go中new與make的區別
- Golang中除了加Mutex鎖以外還有哪些方式安全讀寫共享變量
- 無緩沖Chan的發送和接收是否同步
- Golang并發機制以及它所使用的CSP并發模型.
- Golang中常用的并發模型
- Go中對nil的Slice和空Slice的處理是一致的嗎
- 協程和線程和進程的區別
- Golang的內存模型中為什么小對象多了會造成GC壓力
- Go中數據競爭問題怎么解決
- 什么是channel,為什么它可以做到線程安全
- Golang垃圾回收算法
- GC的觸發條件
- Go的GPM如何調度
- 并發編程概念是什么
- Go語言的棧空間管理是怎么樣的
- Goroutine和Channel的作用分別是什么
- 怎么查看Goroutine的數量
- Go中的鎖有哪些
- 怎么限制Goroutine的數量
- Channel是同步的還是異步的
- Goroutine和線程的區別
- Go的Struct能不能比較
- Go的defer原理是什么
- Go的select可以用于什么
- Context包的用途是什么
- Go主協程如何等其余協程完再操作
- Go的Slice如何擴容
- Go中的map如何實現順序讀取
- Go中CAS是怎么回事
- Go中的逃逸分析是什么
- Go值接收者和指針接收者的區別
- Go的對象在內存中是怎樣分配的
- 棧的內存是怎么分配的
- 堆內存管理怎么分配的
- 在Go函數中為什么會發生內存泄露
- G0的作用
- Go中的鎖如何實現
- Go中的channel的實現
- 棧的內存是怎么分配的2
- 堆內存管理怎么分配的2
- Go中的map的實現
- Go中的http包的實現原理
- Goroutine發生了泄漏如何檢測
- Go函數返回局部變量的指針是否安全
- Go中兩個Nil可能不相等嗎
- Goroutine和KernelThread之間是什么關系
- 為何GPM調度要有P
- 如何在goroutine執行一半就退出協程
- Mysql基礎
- Mysql索引用的是什么算法
- Mysql事務的基本要素
- Mysql的存儲引擎
- Mysql事務隔離級別
- Mysql高可用方案有哪些
- Mysql中utf8和utf8mb4區別
- Mysql中樂觀鎖和悲觀鎖區別
- Mysql索引主要是哪些
- Mysql聯合索引最左匹配原則
- 聚簇索引和非聚簇索引區別
- 如何查詢一個字段是否命中了索引
- Mysql中查詢數據什么情況下不會命中索引
- Mysql中的MVCC是什么
- Mvcc和Redolog和Undolog以及Binlog有什么不同
- Mysql讀寫分離以及主從同步
- InnoDB的關鍵特性
- Mysql如何保證一致性和持久性
- 為什么選擇B+樹作為索引結構
- InnoDB的行鎖模式
- 哈希(hash)比樹(tree)更快,索引結構為什么要設計成樹型
- 為什么索引的key長度不能太長
- Mysql的數據如何恢復到任意時間點
- Mysql為什么加了索引可以加快查詢
- Explain命令有什么用
- Redis基礎
- Redis的數據結構及使用場景
- Redis持久化的幾種方式
- Redis的LRU具體實現
- 單線程的Redis為什么快
- Redis的數據過期策略
- 如何解決Redis緩存雪崩問題
- 如何解決Redis緩存穿透問題
- Redis并發競爭key如何解決
- Redis的主從模式和哨兵模式和集群模式區別
- Redis有序集合zset底層怎么實現的
- 跳表的查詢過程是怎么樣的,查詢和插入的時間復雜度
- 網絡協議基礎
- TCP和UDP有什么區別
- TCP中三次握手和四次揮手
- TCP的LISTEN狀態是什么
- 常見的HTTP狀態碼有哪些
- 301和302有什么區別
- 504和500有什么區別
- HTTPS和HTTP有什么區別
- Quic有什么優點相比Http2
- Grpc的優缺點
- Get和Post區別
- Unicode和ASCII以及Utf8的區別
- Cookie與Session異同
- Client如何實現長連接
- Http1和Http2和Grpc之間的區別是什么
- Tcp中的拆包和粘包是怎么回事
- TFO的原理是什么
- TIME_WAIT的作用
- 網絡的性能指標有哪些