01mysql
概述sql
數據庫是一個多用戶使用的共享資源。當多個用戶併發地存取數據時,在數據庫中就會產生多個事務同時存取同一數據的狀況。若對併發操做不加控制就可能會讀取和存儲不正確的數據,破壞數據庫的一致性。加鎖是實現數據庫併發控制的一個很是重要的技術。當事務在對某個數據對象進行操做前,先向系統發出請求,對其加鎖。加鎖後事務就對該數據對象有了必定的控制,在該事務釋放鎖以前,其餘的事務不能對此數據對象進行更新操做。shell
OLTP 場景下一般要求具備很高的併發性。併發事務實際上取決於資源的使用情況,原則上應儘可能減小對資源的鎖定時間,減小對資源的鎖定範圍,從而可以儘可能增長併發事務的數量,那麼影響併發的因素有哪些呢?本文將從巨杉分佈式數據庫自己的機制以及隔離級別、數據庫鎖、參數、及實際例子進行詳解,讀完本文將對巨杉數據庫併發性與鎖機制有一個初步的瞭解。數據庫
02ubuntu
隔離級別與併發性服務器
在單用戶環境中,每一個事務都是順序執行的,而不會遇到與其餘事務的衝突。可是,在多用戶環境下,多個事務併發執行。所以每一個事務都有可能與其餘正在運行的事務發生衝突。有可能與其餘事務發生衝突的事務稱爲交錯的或並行的事務,而相互隔離的事務稱爲串行化事務,這意味着同時運行它們的結果與一個接一個連續地運行它們的結果沒有區別。在多用戶環境下,在使用並行事務時,會發生四種現象:session
丟失更新:這種狀況發生在兩個事務讀取並嘗試更新同一數據時,其中一個更新會丟失。例如:事務 1 和事務 2 讀取同一行數據,並都根據所讀取的數據計算出該行的新值。若是事務 1 用它的新值更新該行之後,事務 2 又更新了同一行,則事務 1 所執行的更新操做就丟失了。架構
髒讀:當事務讀取還沒有提交的數據時,就會發生這種狀況。例如:事務 1 更改了一行數據,而事務 2 在事務1 提交更改以前讀取了已更改的行。若是事務 1 回滾該更改,則事務 2 就會讀取被認爲是未曾存在的數據。併發
不可重複的讀:當一個事務兩次讀取同一行數據,但每次得到不一樣的數據值時,就會發生這種狀況。例如:事務 1 讀取了一行數據,而事務 2 在更改或刪除該行後提交了更改。當事務 1 嘗試再次讀取該行時,它會檢索到不一樣的數據值(若是該行已經被更新的話),或發現該行不復存在了(若是該行被刪除的話)。mvc
幻像:當最初沒有看到某個與搜索條件匹配的數據行,而在稍後的讀操做中又看到該行時,就會發生這種狀況。例如:事務 1 讀取知足某個搜索條件的一組數據行,而事務 2 插入了與事務 1 的搜索條件匹配的新行。若是事務 1 再次執行產生原先行集的查詢,就會檢索到不一樣的行集。
維護數據庫的一致性和數據完整性,同時又容許多個應用程序同時訪問同一數據,這樣的特性稱爲併發性。巨杉數據庫目前經過事務、隔離級別、鎖等機制來對併發性進行控制,它決定在第一個事務訪問數據時,如何對其餘事務鎖定或隔離該事務所使用的數據。目前巨杉數據庫支持如下隔離級別來實現併發性:
讀未提交(ReadUncommitted):該隔離級別指即便一個事務的更新語句沒有提交,可是別的事務能夠讀到這個改變,幾種異常狀況均可能出現。會出現讀取的數據是不對的。
讀已提交(Read Committed):該隔離級別指一個事務只能看到其餘事務的已經提交的更新,看不到未提交的更新,消除了髒讀和第一類丟失更新,這是大多數數據庫的默認隔離級別。保證了一個事務不會讀到另外一個並行事務已修改但未提交的數據,避免了「髒讀取」,但不能避免「幻讀」和「不可重複讀取」。該級別適用於大多數系統。
讀穩定性(RepeatableStability):該隔離級別指一個事務中進行兩次或屢次一樣的對於數據內容的查詢,獲得的結果是同樣的。假設SQL語句中包括查詢條件, 則會對所有符合條件的紀錄加對應的鎖。假設沒有條件語句。也就是對錶中的所有記錄進行處理。則會對所有的紀錄加鎖。
可重複讀(Repeatable Read):REPEATABLE READ隔離級解決了READUNCOMMITTED隔離級致使的問題。它確保同一事務的多個實例在併發讀取數據時,會「看到一樣的」數據行。不過理論上,這會致使另外一個棘手問題:幻讀(Phantom Read)。簡單來講,幻讀指當用戶讀取某一範圍的數據行時,另外一個事務又在該範圍內插入了新行,當用戶再讀取該範圍的數據行時,會發現有新的「幻影」行。數據庫存儲引擎能夠經過多版本併發控制 (Multiversion Concurrency Control)機制解決了幻讀問題,如MySQL的InnoDB和Falcon。巨杉數據庫對於多版本控制(MVCC)技術是經過採用事務鎖、內存老版本以及磁盤迴滾段重建老版本的設計來實現。此架構設計的理論基礎是經過對內存結構的合理利用,存儲數據和索引的老版本信息,從而實現數據的快速的併發訪問。
03
數據庫鎖參數與併發性實踐
事務做爲一個完整的工做單元執行,事務中的操做要麼所有執行成功要麼所有執行失敗。SequoiaDB事務中的操做只能是插入數據、修改數據以及刪除數據,在事務過程當中執行的其它操做不會歸入事務範疇,也就是說事務回滾時非事務操做不會被執行回滾。若是一個表或表空間中有數據涉及事務操做,則該表或表空間不容許被刪除。
事務開啓、提交與回滾
在SDB中,關於事務啓停的配置項以下:
默認狀況下,SequoiaDB 全部節點的事務功能都是開啓的。若用戶不須要使用事務功能,可參考如下方法,關閉事務功能。
步驟1:經過sdb shell設置集羣全部節點都關閉事務。
db.updateConf( { transactionon: false }, { Global: true } )
步驟2:在集羣每臺服務器上都重啓 SequoiaDB 的全部節點。
[sdbadmin@ubuntu-dev1 ~]$ /opt/sequoiadb/bin/sdbstop -t all
[sdbadmin@ubuntu-dev1 ~]$ /opt/sequoiadb/bin/sdbstart -t all
注意:
開啓及關閉節點的事務功能都要求重啓該節點。
SequoiaDB 事務支持的操做以下:
寫事務操做:INSERT、UPDATE、DELETE。
讀事務操做:QUERY。
SequoiaDB的其它操做(如:建立表、建立索引、建立並讀寫LOB等其它非 CRUD 操做)不在事務功能的考慮範圍。
支持隔離級別配置參數及取值以下:
能夠經過如下方式修改:
db.updateConf( { transisolation: 1 }, { Global: true } )
注意:該參數在線生效,會在下一次事務中生效
經過 "transBegin"、"transCommit" 及"transRollback" 方法,用戶能夠在一個事務中,對若干個操做進行事務控制。其使用方式以下:
db.transBegin()
操做1
操做2
操做3
...
db.transCommit() or db.transRollback()
在上述使用模式中,用戶必須顯式調用"transCommit" 及 "transRollback" 方法來結束當前事務。然而,對於寫事務操做,若在操做過程發生錯誤,數據庫配置中的 transautorollback 配置項能夠決定當前會話全部未提交的寫操做是否自動回滾。transautorollback 的描述以下:
注意:該配置項只有在事務功能開啓(即 transactionon 爲 true )的狀況下才生效。
默認狀況下,transautorollback 配置項的值爲 true。因此,當寫事務操做過程出現失敗時,當前事務全部未提交的寫操做都將被自動回滾。
事務自動提交
數據庫配置中,關於事務自動提交的配置項以下:
事務自動提交功能默認狀況下是關閉的。當transautocommit 設置爲 true 時,事務自動提交功能將開啓。此時,使用事務存在如下兩點不一樣:
用戶不須要顯式調用 "transBegin" 和"transCommit" 或者 "transRollback" 方法來控制事務的開啓、提交或者回滾。
事務提交或者回滾的範圍僅僅侷限於單個操做。當單個操做成功時,該操做將被自動提交;當單個操做失敗時,該操做將被自動回滾。
例如,以下操做中:
/ transautocommit 設置爲 true /
db.foo.bar.update({$inc:{"salary": 1000}}, {"department": "A"}) // 更新 1
db.foo.bar.update({$inc:{"salary": 2000}}, {"department": "B"}) // 更新 2
db.foo.bar.update({$inc:{"salary": 3000}}, {"department": "C"}) // 更新 3
...
更新 一、更新 二、更新 3 分別爲獨立的操做。假設更新 1 和 更新 2 操做成功,而更新 3失敗。那麼更新 1 和 更新 2 修改的記錄將所有被自動提交。而更新 3 修改的記錄將所有被自動回滾。
其它配置
數據庫配置中,關於事務的其它主要配置項以下:
調整設置
當用戶但願調整事務的設置時(如:是否開啓事務、調整事務配置項等),有以下 3 種方式供用戶選擇使用:
用戶能夠將數據庫配置描述的事務配置項,配置到集羣全部(或者部分)節點的配置文件中。若修改的配置項要求重啓節點才能生效,用戶需重啓相應的節點。
使用 updateConf()命令在 sdb shell 中修改集羣的事務配置項。若修改的配置項要求重啓節點才能生效,用戶需重啓相應的節點。
使用 setSessionAttr()命令在會話中修改當前會話的事務配置項。該設置只在當前會話生效,並不影響其它會話的設置狀況。
示例:
創建數據庫以及表
mysql> use company;
Database changed
mysql> create table t1 (a int,b int);
Query OK, 0 rows affected (0.03 sec)
1)事務提交與回滾
例子1:
使用事務回滾插入操做。事務回滾後,插入的記錄將被回滾,集合中無記錄:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t1 values(1,1);
Query OK, 1 row affected (0.08 sec)
mysql> select from t1;
+------+------+
| a | b |
+------+------+
| 1 | 1 |
+------+------+
1 row in set (0.00 sec)
mysql> rollback;
Query OK, 0 rows affected (0.01 sec)
mysql> select from t1;
Empty set (0.00 sec)
例子2:
使用事務提交插入操做。提交事務後,插入的記錄將被持久化到數據庫:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t1 values(1,1);
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from t1;
+------+------+
| a | b |
+------+------+
| 1 | 1 |
+------+------+
1 row in set (0.01 sec)
2)隔離級別爲RU併發與鎖
例子3:
在隔離級別爲RU(transisolation 設置爲0)的狀況下,設置當前會話級(會話1及會話2同時設置)隔離級別爲read uncommitted :
mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)
在窗口1:會話1對該表寫入數據,並不提交。
mysql> create table t3(a int,b int);
Query OK, 0 rows affected (0.02 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t3 values(1,1);
Query OK, 1 row affected (0.03 sec)
在窗口2:會話2對該表進行查詢,查到的是未提交的數據。
mysql> select * from t3;
+------+------+
| a | b |
+------+------+
| 1 | 1 |
+------+------+
1 row in set (0.01 sec)
小結:因爲採用了隔離級別是RU,容許髒讀,在第二個會話中不會產生鎖等待,而直接會讀到未提交的數據。該隔離級別建議設置在以讀爲主的歷史數據平臺應用中,在真實的OLTP環境中,不能知足業務需求。這樣業務查詢會讀取到未提交事務的修改,若是事務發生回滾,那麼讀取的數據是錯誤的。不能知足一致性的要求。
3)隔離級別爲RC併發與鎖
RR隔離級別的實現概述:
巨杉數據庫在RC隔離級別上除了支持傳統關係型數據庫的讀已提交之外,經過MVCC多版本訪問的方式支持讀取最後一次提交的版本而不會產生鎖等待,從而提升業務的並行處理能力。
例子4:在隔離級別爲RC(transisolation 設置爲1,translockwait爲true)的狀況下,看看併發狀況:
首先,修改隔離級別爲1,translockwait爲true該修改將在下一次鏈接的時候生效。
db.updateConf({transisolation:1},{Global:true});
Takes 0.051656s.
db.updateConf({translockwait:true},{Global:true});
Takes 0.041699s.
db.updateConf({transactiontimeout:30},{Global:true});
Takes 0.099934s.
db.updateConf({transautocommit:true},{Global:true});
Takes 0.040183s.
備註:也能夠經過mysql端進行當前session隔離級別參數的修改。
設置當前會話級(會話1及會話2同時設置)隔離級別爲read committed :
mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
在窗口1:事務1對該表寫入數據,並不提交。
mysql> create table t4 (a int,b int);
Query OK, 0 rows affected (0.09 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t4 values(1,1);
Query OK, 1 row affected (0.03 sec)
在窗口2:事務2對該表進行查詢,能夠看到一直會處於等待鎖的狀態,直到鎖超時(transactiontimeout設置爲30秒)退出。
mysql> select * from t4;
ERROR 1030 (HY000): Got eror 40013 from storage engine
經過捉取鎖的快照,能夠看到第一個事務持有鎖的信息,持有該表上的IX,IS鎖以及記錄上的X鎖。以下圖所示:
而第二個事務等待鎖的狀況,在等鎖該表記錄上的S鎖。以下圖所示:
小結:因爲採用了隔離級別是RC 而且translockwait設置爲true的狀況下,在第二個事務中會產生鎖等待,直到第一個事務釋放該表上的行鎖,第二個事務才能執行,不然會一直等待到鎖超時退出爲止。這也是大多數傳統關係型數據庫的默認隔離級別。
例子5:
在隔離級別爲RC(transisolation 設置爲1,translockwait爲false)的狀況下,看看併發狀況:
咱們先來看看translockwait設置爲false的說明: 不等待記錄鎖,直接從系統讀取最後一次提交的版本。
設置SDB參數配置:
db.updateConf({translockwait:false},{Global:true});
Takes 0.041699s.
備註:能夠經過mysql端進行當前session隔離級別參數的修改。
設置當前會話級(會話1及會話2同時設置)隔離級別爲read committed :
mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
在窗口1:事務1對該表寫入數據,並不提交。
mysql> create table t5 (a int,b int);
Query OK, 0 rows affected (0.09 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t5 values(1,1);
Query OK, 1 row affected (0.03 sec)
在窗口2:事務2對該表進行查詢,能夠看到立刻返回,並無發生鎖等待的狀況。這時候查到的數據是最後一次提交的版本
mysql> select * from t5;
Empty set (0.01 sec)
小結:因爲採用了隔離級別是RC 而且translockwait設置爲false的狀況下,在第二個事務不會產生鎖等待,而是會讀到最後一次版本已提交的數據。經過鎖快照也能夠看到沒有任何鎖等待的狀況出現。該隔離級別設置適用於絕大多數的OLTP場景。
4)隔離級別爲RS併發與鎖
例子6:
在隔離級別爲RS(transisolation 設置爲2)的狀況下,看看併發狀況:
db.updateConf({transisolation:1},{Global:true});
Takes 0.051656s.
在窗口1:事務1對該表進行查詢數據,不提交。
mysql> create table t6 (a int,b int,primary key(a));
Query OK, 0 rows affected (0.09 sec)
mysql> insert into t6 values(1,1);
Query OK, 1 row affected (0.03 sec)
mysql> insert into t6 values(2,1);
Query OK, 1 row affected (0.03 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t6;
+------+------+
| a | b |
+------+------+
| 1 | 1 |
| 2 | 1 |
+------+------+
1 row in set (0.01 sec)
在窗口2:事務2對該表進行更新,能夠看處處於鎖等待的狀態。最終鎖超時事務進行回滾:
mysql> egin;
Query OK, 0 rows affected (0.00 sec)
mysql> update t6 set b=11 where b=1;
ERROR 1030 (HY000): Got error 40013 from storage engine
經過捉取鎖的快照,能夠看到第一個事務持有鎖的狀況,查詢拿到了該表上的S鎖。以下圖所示:
而事務2須要取到該表上的X鎖而產生了等待,以下圖所示:
小結:因爲採用了隔離級別是RS ,在第二個事務更新的事務會產生鎖等待,任何事務查找的記錄都不容許更新,直到該讀取的表的鎖被釋放。經過鎖快照也能夠看到有鎖等待的狀況出現。RS場景併發性較差,通常適應於總賬計算系統系統。查到的數據該事務不提交,側不容許被修改。
5)隔離級別爲RR併發與鎖
RR隔離級別的實現概述:
在多版本控制技術的事務鎖實現中,RR(可重複讀)配置下的讀操做能夠在使用完記錄以後當即釋放鎖,不須要一直持有,直到事務提交或者回滾。可是寫事務操做則須要一直持有插入、更改和刪除的鎖,直到事務完成提交或者回滾。巨杉數據庫鎖的實現是採用悲觀鎖機制,與傳統關係型數據庫的採用的主流鎖機制相似。
在多版本控制技術的實現中,除了引入悲觀鎖的機制之外,巨杉數據庫還採用了內存老版本機制提高數據庫併發訪問及操做的能力。內存老版本是經過在記錄鎖上附加有一個存儲原版本數據和索引相關的結構,於內存中存儲了老版本的數據。
如下經過實例操做進行詳解:
例子7:
在隔離級別爲RR(transisolation 設置爲3)的狀況下,看看併發狀況:
db.updateConf({transisolation:3},{Global:true});
Takes 0.051656s.
db.updateConf({mvccon:true},{Global:true})
Takes 0.156197s.
db.updateConf({globtranson:true},{Global:true})
Takes 0.051241s.
備註:打開RR隔離,除了transisolation 設置爲3之外,須要修改以上多二個參數。mvccon及globtranson這二個參數爲true。經過mysql端也能夠直接進行設置。
經過mysql直接設置當前會話級(會話1及會話2同時設置)隔離級別爲REPEATABLEREAD;
mysql> set session transaction isolation level REPEATABLE READ;
Query OK, 0 rows affected (0.00 sec)
在窗口1:事務1對該表rr進行查詢數據,不提交
mysql> create table rr (a int);
Query OK, 0 rows affected (0.06 sec)
mysql> insert into rr values(1);
Query OK, 1 row affected (0.19 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from rr;
+------+
| a |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
在窗口2:事務2對該表rr(事務1第一次查詢後)進行數據更新後提交。
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update rr set a=2 where a=1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
在窗口1:事務1對該表rr進行再次查詢數據,查詢到的數據能夠看到不會因爲事務2的更新提交而改變,而是讀到事務開始前的版本數據。
mysql> select * from rr;
+------+
| a |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
經過捉取鎖的快照,能夠看到第一個查詢的事務1在整個事務的查詢中沒有持任何鎖,而事務2更新的操做持用該表的行鎖。以下所示:
事務1持有鎖的狀況(未持有鎖):
事務2持有鎖的狀況,持有該表的X鎖,以下圖所示:
小結:因爲採用了隔離級別是RR ,任何查詢都不會持用鎖,也不會等鎖,能夠看到在第二個事務更新的操做不會影響事務1,任何更新的操做不會影響查詢,因爲事務1是在事務2以前執行查詢,當前事務1始終查到的是rr表的事務2的更新前版本。該隔離級別適應於大併發查詢的交易場景,能有效提升整個應用的併發性。
05
總結
巨杉數據庫完整支持傳統關係型數據庫的幾種經常使用隔離級別,可知足全部核心生產場景(OLTP及OLAP等場景)需求。創新性採用事務鎖、內存老版本以及磁盤迴滾段重建老版本的設計來實現了多版本併發控制技術。經過對內存結構的合理利用,存儲數據和索引的老版本信息,從而實現多版本數據的快速的併發訪問。