事務能夠是一個很是簡單的SQL構成,也能夠是一組複雜的SQL語句構成。事務是訪問而且更新數據庫中數據的一個單元,在事務中的操做,要麼都修改,要麼都不作修改,這就是事務的目的,也是事務模型區別於其餘模型的重要特徵之一。 事務的原子性:原子是不可分割的,事務不可分割(沒有commit數據不能被讀到).java 事務的持久性:在commit以後,不能丟數據.(就是在提交後,數據必須落盤redo落盤).python 事務的隔離性:在數據庫裏面,各個事務之間不能互相影響.mysql 事務的一致性:事務先後,不能違反mysql的約束.sql |
1.原子性(Atomicity)數據庫
原子性是指是事務是不可分割的一部分,一個事務內的任務要麼所有執行成功,要麼所有不執行,不存在執行一部分的狀況。 能夠將整個取款流程當作原子操做,要麼取款成功,要麼取款失敗。緩存 1. 咱們可使用取錢的例子,來說解這一特性安全 2. 登陸ATM機器session 3. 從遠程銀行的數據庫中,獲取帳戶的信息併發 4. 用戶在ATM上輸入欲取出的金額mvc 5. 從遠程銀行的數據庫中,更新帳戶信息 6. ATM機器出款 7. 用戶取錢 整個取錢操做,應該視爲原子操做,要麼都作,要麼都不作,不能用戶錢還沒出來,可是銀行卡的錢已經被扣除了。使用事務模型能夠保證該操做的一致性 |
是指事務的完整性約束沒有被破壞,若是遇到了違反約束的狀況,數據庫會被自動回滾。一致性是指數據庫從一種狀態轉變爲另外一種狀態。在開始事務和結束事務後,數據庫的約束性沒有被破壞。例如,數據庫表中的用戶ID列爲惟一性約束,即在表中姓名不能重複. |
事物的隔離性要求每一個事務的操做,不會受到另外一個事務的影響。隔離狀態執行事務,使他們好像是系統在給定時間內執行的惟一操做.這種屬性有時稱爲串行化. |
事務一旦提交,那麼結果就是持久性的,不會被回滾。即便發生宕機,數據庫也能夠作數據恢復。 |
說明:隔離性經過鎖實現,原子性、一致性、持久性經過數據庫的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才能生效. |
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 ################################################################### |
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鎖,這稱之爲鎖兼容. |