1.事務的四種特性(ACID)
| 事務可以是一個非常簡單的SQL構成,也可以是一組復雜的SQL語句構成。事務是訪問並且更新數據庫中數據的一個單元,在事務中的操作,要么都修改,要么都不做修改,這就是事務的目的,也是事務模型區別於其他模型的重要特征之一。 事務的原子性:原子是不可分割的,事務不可分割(沒有commit數據不能被讀到). 事務的持久性:在commit之后,不能丟數據.(就是在提交后,數據必須落盤redo落盤). 事務的隔離性:在數據庫里面,各個事務之間不能互相影響. 事務的一致性:事務前后,不能違反mysql的約束. |
1.原子性(Atomicity)
| 原子性是指是事務是不可分割的一部分,一個事務內的任務要么全部執行成功,要么全部不執行,不存在執行一部分的情況。 可以將整個取款流程當做原子操作,要么取款成功,要么取款失敗。 1. 我們可以使用取錢的例子,來講解這一特性 2. 登錄ATM機器 3. 從遠程銀行的數據庫中,獲取賬戶的信息 4. 用戶在ATM上輸入欲取出的金額 5. 從遠程銀行的數據庫中,更新賬戶信息 6. ATM機器出款 7. 用戶取錢 整個取錢操作,應該視為原子操作,要么都做,要么都不做,不能用戶錢還沒出來,但是銀行卡的錢已經被扣除了。使用事務模型可以保證該操作的一致性 |
2.一致性(Consistency)
| 是指事務的完整性約束沒有被破壞,如果遇到了違反約束的情況,數據庫會被自動回滾。一致性是指數據庫從一種狀態轉變為另一種狀態。在開始事務和結束事務后,數據庫的約束性沒有被破壞。例如,數據庫表中的用戶ID列為唯一性約束,即在表中姓名不能重復. |
3.隔離性(isolation)
| 事物的隔離性要求每個事務的操作,不會受到另一個事務的影響。隔離狀態執行事務,使他們好像是系統在給定時間內執行的唯一操作.這種屬性有時稱為串行化. |
4.持久性(Durability)
| 事務一旦提交,那么結果就是持久性的,不會被回滾。即使發生宕機,數據庫也可以做數據恢復。 |
說明:隔離性通過鎖實現,原子性、一致性、持久性通過數據庫的redo和undo來完成
支持事務的數據庫:InnoDB、NDBCluster、TokuDB
不支持事務的數據庫:MyISAM、MEMORY
5.事物的實現
| 重做日志(redo)用來實現事務的持久性,有兩部分組成,一是內存中的重做日志,一個是硬盤上的重做日志文件。innodb是支持事務的存儲引擎,通過日志先行WAL,來實現數據庫的事務的特性,在一個事務提交的時候,並不是直接對內存的臟數據進行落盤,而是先把重做日志緩沖中的日志寫入文件,然后再提交成功。這樣保證了即使在斷電的情況下,依然可以依靠redo log進行數據的恢復與重做。只要是提交的事務,在redo中就會有記錄,數據庫斷電后重啟,數據庫會對提交的事務,但是沒有寫入硬盤的臟數據,利用redo來進行重做。 還要一個保證事務的是undo,undo有兩個作用: 1.實現事務的回滾 2.實現mvcc的快照讀取 redo是物理邏輯日志,計算頁的物理修改操作. undo是邏輯記錄,記錄了行的操作內容. 兩階段提交:先寫redo -buffer再寫binlog 並落盤最后落盤redo. |
6.lsn號
| LSN(log sequence number)日志序列號 查看LSN信息: show engine innodb status\G; LSN實際上對應日志文件的偏移量,新的LSN=舊的LSN + 寫入的日志大小. 日志文件刷新后,LSN不會進行重置 Log sequence number:當前系統LSN最大值,新的事務日志LSN將在此基礎上生成(LSN1+新日志的大小) Log flushed up to:當前已經寫入日志文件的LSN Pages flushed up to:當前最舊的臟頁數據對應的LSN,寫Checkpoint的時候直接將此LSN寫入到日志文件 Last checkpoint at:當前已經寫入Checkpoint的LSN |
| redo作用 Redo介紹: 1. DML操作導致的頁面變化,均需要記錄Redo日志(物理日志) 2. 在頁面修改完成之后,在臟頁刷出磁盤之前,寫入Redo日志; 3. 日志先行(WAL),日志一定比數據頁先寫回磁盤; 4. 聚簇索引/二級索引/Undo頁面修改,均需要記錄Redo日志; 為了管理臟頁,在 Buffer Pool 的每個instance上都維持了一個flush list,flush list 上的 page 按照修改這些page 的LSN號進行排序。因此定期做redo checkpoint點時,選擇的 LSN 總是所有 bp instance 的 flush list 上最老的那個page(擁有最小的LSN)。由於采用WAL的 策略,每次事務提交時需要持久化 redo log 才能保證事務不丟。而延遲刷臟頁則起到了合並多次修改的效果,避免頻繁寫數據文件造成的性能問題。 REDO的作用:提高性能和做crash recovery 提高性能: 1. 日志用來記錄buffer pool中頁的page修改的,每次數據提交只要寫redo日志就可以,不需要每次都寫臟頁。 2. 通常一個數據頁是16KB,如果不寫日志,每次的寫入還是16kb,即使修改很少數據,仍然要全部落盤,性能影響非常嚴重。 3. 如果沒有日志,每次都會刷臟頁,臟頁的位置導致的IO是隨機IO,而redo的數據頁的大小是512字節,這樣非常契合硬盤的塊大小,可以進行順序IO,這樣可以保證順序IO,同時可以大大提高IOPS 做crash recovery: 1. 數據庫重啟后,利用redo log進行數據庫恢復工作,比對redolog LSN和數據頁的LSN,如果數據頁LSN低於REDO LOG LSN就會進行數據頁實例恢復 |
| 1. 用於回滾事務 2. 保證mvcc多版本高並發控制 innodb把undo分為兩類 1. 新增undo 2. 修改undo 分類依據就是是否需要做purge操作. insert在事務執行完成后,回滾記錄就可以丟掉了。但是對於更新和刪除操作而言,在完成事務后,還需要為MVCC提供服務,這些日志就被放到一個history list,用於MVCC以及等待purge。 undo日志的正確性是通過redo來保證的,所以在數據庫恢復的時候,需要先恢復redo,在所有數據塊都保證一致性的情況下,在進行undo的邏輯操作。 |
9.檢查點(checkpoint)
| 檢查點就是落臟頁的點,本質是一個特殊的lsn. 檢查點解決的問題: 1. 縮短數據庫恢復時間 2. 緩沖池不夠用的時候,刷新臟頁到磁盤 3. 重做日志不夠用的時候,刷新臟頁 當數據庫發生宕機的時候,數據庫不需要恢復所有的頁面,因為檢查點之前的頁面都已經刷新回磁盤了。故數據庫只需要對檢查點以后的日志進行恢復,這就大大減少了恢復時間。 檢查點的類型: 檢查點分為兩種類型,一種是sharp檢查點,一種是fuzzy檢查點 sharp checkpoint落盤條件: 1. 關閉數據庫的時候設置 innodb_fast_shutdown=1,在關閉數據庫的時候,會刷新所有臟頁到數據庫內。 fuzzy checkpoint在數據庫運行的時候,進行頁面的落盤操作,不過這種模式下,不是全部落盤,而是落盤一部分數據。 Fuzzy落盤的條件: 1. master thread checkpoint: master每一秒或者十秒落盤 2. sync check point: redo 不可用的時候,這時候刷新到磁盤是從臟頁鏈表中刷新的。 3. Flush_lru_list check point : 刷新flush list的時候 落盤的操作是異步的,因此不會阻塞其他事務執行。 檢查點的作用: 縮短數據庫的恢復時間 緩沖池不夠用的時候,將臟頁刷新到磁盤 重做日志不可用的時候,刷新臟頁(循環使用redo文件,當舊的redo要被覆蓋的時候,需要刷新臟頁,造成檢查點) |
10.兩階段提交
| MySQL二階段提交流程: 事務的提交主要分三個主要步驟: 1.Storage Engine(InnoDB) transaction prepare階段:存儲引擎的准備階段,寫redo-buffer 此時SQL已經成功執行,並生成xid信息及redo和undo的內存日志。 2.Binary log日志提交:寫binlog並落盤. write()將binary log內存日志數據寫入文件系統緩存。 fsync()將binary log文件系統緩存日志數據永久寫入磁盤。 3.Storage Engine(InnoDB)內部提交:落盤redo日志. 修改內存中事務對應的信息,並且將日志寫入重做日志緩沖。 調用fsync將確保日志都從重做日志緩沖寫入磁盤。 一旦步驟2中的操作完成,就確保了事務的提交,即使在執行步驟3時數據庫發送了宕機。 即binlog落盤成功,就算redo未落盤成功,那么事務也算是提交成功了. binlog落盤條件:參數sync_binlog: 0每秒落盤,1每次commit落盤 n 每n個事物落盤 此外需要注意的是,每個步驟都需要進行一次fsync操作才能保證上下兩層數據的一致性。步驟2的fsync參數由sync_binlog控制,步驟2的fsync由參數innodb_flush_log_at_trx_commit控制。(雙1配置) 兩階段提交:先寫redo -buffer再寫binlog 並落盤最后落盤redo-buffer. 最終:mysql在落盤日志的時候,先落盤binlog,再落盤redo. |
| 當事務在binlog階段crash,此時日志還沒有成功寫入到磁盤中,啟動時會rollback此事務。 當事務在binlog日志已經fsync()到磁盤后crash,但是InnoDB沒有來得及commit,此時MySQL數據庫recovery的時候將會從二進制日志的Xid(MySQL數據庫內部分布式事務XA)中獲取提交的信息重新將該事務重做並commit使存儲引擎和二進制日志始終保持一致。 總結起來說就是如果一個事物在prepare log階段中落盤成功,並在MySQL Server層中的binlog也寫入成功,那這個事務必定commit成功。 |
12.事物隔離級別
| 隔離級別 事物 問題 縮寫 READ UNCOMMITED 未提交讀 臟讀 RU READ COMMITED 提交讀 幻讀 RC REPEATEABLE READ 可重復讀 RR SERIALIZABLE 序列化 |
使用RR級別的話,可以保證數據庫沒有幻讀,但是在該模式下,會增加鎖競爭,造成數據庫並發能力的下降。在RC模式下,沒有next_lock的存在,即使在沒有索引的情況下,也很難造成大規模的鎖表而導致的死鎖問題。
13.自動提交參數設置
| Mysql會自動提交 (mysql在mysql客戶端是自動提交,但是java python里面不自動提交) 取消自動提交: set autocommit=off; |
14.查看mysql隔離級別
| show variables like '%iso%';
|
15.修改事物隔離級別
| 設置全局參數: set global transaction isolation level read uncommitted; Set global transaction isolation level read committed; Set global transaction isolation level repeatable read; 設置會話級別參數: set session transaction isolation level read uncommitted; Set session transaction isolation level read committed; Set session transaction isolation level repeatable read; 查看全局的MySQL GLOBAL的配置的隔離級別。 global參數設置后,需要新開啟session才能生效. |
4.事務隔離級別對應問題:(臟讀,幻讀,不可重復讀)
| RU --------- 產生臟讀問題 RC ---------- 解決臟讀問題,但產生幻讀和不可重復讀問題. RR --------- 避免幻讀和不可重復讀問題.,待容易鎖等待. SERIALIZABLE -----------序列化,串行讀寫. 在 REPEATEABLE READ下,其他事務對於數據的修改(update,delete)不會影響本事務對於數據的讀取,會避免幻讀的產生,幻讀就是在一個事務內,讀取到了不同的數據行數結果。 數據越安全,相對來說,數據庫的並發能力越弱(並不代表總體性能越弱)。 臟讀(Drity Read):事務T1修改了一行數據,事務T2在事務T1提交之前讀到了該行數據。 不可重復讀(Non-repeatable read): 事務T1讀取了一行數據。 事務T2接着修改或者刪除了該行數據,當T1再次讀取同一行數據的時候,讀到的數據時修改之后的或者發現已經被刪除。(針對update/delete操作). 在READ COMMITED下,未被提交的事務不會被讀到,只有被提交的事務的數據,才會被讀取到。(執行兩次相同的SQL得到了不同的結果。),這就造成了幻讀和不可重復讀問題. 幻讀(Phantom Read): 事務T1讀取了滿足某條件的一個數據集,事務T2插入了一行或者多行數據滿足了T1的選擇條件,導致事務T1再次使用同樣的選擇條件讀取的時候,得到了比第一次讀取更多的數據集。(針對insert操作). 幻讀和可重復讀的區別: 幻讀更多的是針對於insert來說,即在一個事務之中,先后的兩次select查詢到了新的數據,新的數據來自於另一個事務的insert,一般稱之為幻讀,通過gap_lock(間隙鎖)來防止產生幻讀(雖然record可以避免數據行被修改,但是卻無法阻止insert,gap_lock鎖定索引間隙,防止了在事務查詢的范圍內的insert情況) 在ru隔離級別下造成臟讀,在rc隔離級別下造成幻讀和不可重復讀. 可重復讀:一般是針對於update和delete來說,可重復讀采用了mvcc多版本控制來實現數據查詢結果本身的不變。 |
5.事物隔離級別示例
1.ru(READ-UNCOMMITED 未提交讀)Ru級別造成了臟讀: session2可以讀取到session1的沒有提交的事務的數據(內存中沒有提交的臟頁). 臟讀的發生至少在RU下,而目前幾乎所有的數據庫幾乎都在RC級以上的隔離界別上。 臟讀實例: 兩個session修改隔離級別: set session transaction isolation level read uncommitted; session1 session2 begin; begin; insert into t1 values(null,'testru') select * from test1.t1;#查詢到了testru數據,造成了臟讀 commit; commit; ######################################################## 2.rc(read committed 已提交讀)Rc級別解決了臟讀.但會造成不可重復讀和幻讀.兩者發生方式一樣,但由於解決方法不同而區分. 解決了臟讀實例:(針對select.) 修改隔離級別: Set session transaction isolation level read committed; session1 session2 begin; begin; insert into test1.t1 values(null,'testrc'); select * from test1.t1;#沒有查到 commit; select * from test1.t1; 查詢到了 commit; ################################################################ 不可重復讀:針對於update/delete來說. 幻讀:針對於insert來說的. 幻讀實例: Set session transaction isolation level read committed; ##################################################### session1 session2 begin; begin; select * from test1.t1;--7條 insert into t1 values() commit; select * from test1.t1 --8 commit; ##################################################### 不可重復讀實例: Set session transaction isolation level read committed; session1 session2 begin; begin; select * from test1.t1 where name=’aa’;--找到1條 Update t1 set name=’bb’ where name=’aa’; commit; select * from test1.t1 where name=’aa’ --沒有找到. commit; ################################################################### 加鎖實例1:(加鎖只針對指定的行,不影響update/insert操作) Set session transaction isolation level read committed; session1 session2 begin; begin; update test1.t1 set name='bbb' ; insert into test1.t1 values(null,'test_rr') 加了鎖,但插入成功. commit; select * from test1.t1 1000w+1 Commit; ############################################################################# 3.rr(repeatable read 可重復讀)RR隔離級別:可重復讀 .repeatable read 在RR模式下GAP_LOCK是默認開啟的. 避免幻讀實例:(insert) set session transaction isolation level repeatable read; session1 session2 begin; begin; select * from test1.t1; insert into t1 values(null,'testrr'); commit; select * from test1.t1;查詢不到testrr commit; select * from test1.t1;查詢到testrr #################################################### 避免不可重復讀實例:(update /delete) set session transaction isolation level repeatable read; session1 session2 begin; begin; select * from test1.t1; update test1.t1 set name='testrr_upd' where name='testrr' commit; select * from test1.t1;查詢不到testrr_upd
commit; select * from test1.t1;查詢到testrr_upd ################################################################### 鎖等待實例:(一定會鎖,update后,除了select,其他操作如insert/update都會被鎖.) set session transaction isolation level repeatable read; session1 session2 begin; begin; update test1.t1 set name='ccc'; insert into test1.t1 values(null,'test_rr')插入被阻塞,進入鎖等待狀態 commit; commit; select * from test1.t1 1000w+1 ################################################################### |
5.MVCC的簡單實現
| MVCC在MySQL的InnoDB中的實現原理: 基於UNDO的多版本快照日志 MySQL InnoDB存儲引擎,實現的是基於多版本的並發控制協議——MVCC (Multi-Version Concurrency Control) (注:與MVCC相對的,是基於鎖的並發控制,Lock-Based Concurrency Control)。 1.MVCC的好處:讀不加鎖,讀寫不沖突.讀不加鎖,讀寫不沖突。在讀多寫少的OLTP應用中,讀寫不沖突是非常重要的,極大的增加了系統的並發性能,這也是為什么現階段,幾乎所有的RDBMS,都支持了MVCC。 2.讀的類型:快照讀和當前讀.在MVCC並發控制中,讀操作可以分成兩類:快照讀 (snapshot read)與當前讀 (current read)。 快照讀: 讀取的是記錄的可見版本 (有可能是歷史版本),不用加鎖。例如 select 就為快照讀. 當前讀:讀取的是記錄的最新版本,並且,當前讀返回的記錄,都會加上鎖,保證其他事務不會再並發修改這條記錄。例如刪除/更新/刪除操作。 過程: 在MVCC讀取數據的過程中,會先對目前的事務和數據行的事務號進行對比,如果發現事務行的事務版本號,已經增長,則說明該行數據已經被其他事務修改,那么就需要根據undo,讀取undo內的歷史版本的相關邏輯操作信息,然后根據邏輯操作信息構建符合當前查詢的數據版本,然后返回結果集(在RC和RR模式下,對於UNDO構建數據塊的版本選擇不同) 通過MVCC,雖然每行記錄都需要額外的存儲空間,更多的行檢查工作以及一些額外的維護工作,但可以減少鎖的使用,大多數讀操作都不用加鎖,讀數據操作很簡單,性能很好,並且也能保證只會讀取到符合標准的行,也只鎖住必要行。 3.一致性非鎖定讀一致性的非鎖定讀就是INNODB存儲引擎通過行多版本控制的方式來讀取當前執行時間數據庫中的數據。如果讀取的行正在執行DELETE或者UPDATE,這個時候讀取操作不會等待所在行的鎖的釋放。INNODB這個時候會讀取一個快照數據。(若讀的數據加鎖了,則讀取其前一個版本的undo日志。) 一致性非鎖定讀取的原理是這樣的: Innodb通過隱藏的回滾指針保存前一個版本的undo日志,通過當前記錄加上undo日志可以構造出記錄的前一個版本,從而實現同一記錄的多個版本。 快照數據就是當前數據行的歷史版本,每個行記錄可能有多個版本 在RC模式下,MVCC會一直讀取最新的快照數據。 在RR模式下,MVCC會讀取本事務開始時候的快照數據。 對於一致性非鎖定讀取,即使被讀取的行已經SELECT ⋯ FOR UPDATE,也是可以進行讀取的。
4.一致性鎖定讀取有些用戶需要采用鎖定讀取的方式來進行讀取保證數據的一致性。 手動在查詢中添加鎖 查詢中使用S鎖: select * from test1.t1 where id=1 lock in share mode; 查詢中添加X鎖: select * from test1.t1 where id=1 for update; 關於鎖等待的參數:參數支持范圍為Session和Global,並且支持動態修改 事務等待獲取資源等待的最長時間,超過這個時間還未分配到資源則會返回應用失敗。 設置鎖等待時間參數: innodb_lock_wait_timeout 30 --單位秒. |
鎖
| Mysql鎖加在索引上. 鎖的作用:將並行的事務變成串行的. 從鎖的顆粒度來說,鎖分:表鎖, 頁鎖, 行鎖。 MySQL中鎖的概念可以等同於:並發控制,序列化,隔離性. 這種用隔離性來描述鎖,就是因為是事務ACID特性中的I,而鎖就是用來實現事務一致性和隔離性的一種常用技術。 當數據庫事務並發各自運行的時候,每個事務的運行不受到其他事務的影響。 簡單的加鎖技術就是對對象加上一個鎖,若訪問該事務的時候,發現已經有鎖,則等待該事務鎖的釋放。 通過多粒度鎖定,保證了數據庫中事務的並發性。 1.意向鎖意向鎖:打算向這個表里的數據加鎖,會提前在表級別加一個意向鎖,加在聚簇索引的根節點. 1. 揭示下一層級請求的鎖的類型 2. IS:事物想要獲得一張表中某幾行的共享鎖 3. IX:事物想要獲得一張表中某幾行的排他鎖 4. InnoDB存儲引擎中意向鎖都是表鎖 假如此時有 事物tx1 需要在 記錄A 上進行加 X鎖 : 1. 在該記錄所在的數據庫上加一把意向鎖IX 2. 在該記錄所在的表上加一把意向鎖IX 3. 在該記錄所在的頁上加一把意向鎖IX 4. 最后在該記錄A上加上一把X鎖 假如此時有 事物tx2 需要對 記錄B (假設和記錄A在同一個頁中)加 S鎖 : 1. 在該記錄所在的數據庫上加一把意向鎖IS 2. 在該記錄所在的表上加一把意向鎖IS 3. 在該記錄所在的頁上加一把意向鎖IS 4. 最后在該記錄B上加上一把S鎖 加鎖是從上往下,一層一層 進行加的. 意向鎖存在意義: · 意向鎖是為了實現多粒度的鎖,表示在數據庫中不但能實現行級別的鎖,還可以實現頁級別的鎖,表級別的鎖以及數據庫級別的鎖 · 如果沒有意向鎖,當你去鎖一張表的時候,你就需要對表下的所有記錄都進行加鎖操作,且對其他事物剛剛插入的記錄(游標已經掃過的范圍)就沒法在上面加鎖了,此時就沒有實現鎖表的功能。 IX,IS是表級鎖,不會和行級的X,S鎖發生沖突。只會和表級的X,S發生沖突,行級別的X和S按照普通的共享、排他規則即可。所以只要寫操作不是同一行,就不會發生沖突。 InnoDB 沒有數據庫級別的鎖,也沒有頁級別的鎖(InnoDB只能在表和記錄上加鎖),所以InnoDB的意向鎖只能加在表上,即InnoDB存儲引擎中意向鎖都是表鎖. 2.行鎖(X和S鎖)對於行鎖,根據其作用類型,可以分為兩類: 共享鎖(S lock) ,允許讀取一個數據,同時允許其他事務對該事務進行更改。 排他鎖(X lock),允許刪除或者更新一條數據,同時不允許其他事務對該事務進行操作。 鎖兼容:當一行獲取S鎖的時候,也可以獲取另一個事務的S鎖,這稱之為鎖兼容. |
