事務必會必知
事務,大家所熟悉的事務(Transaction),基本上會就往Spring事務靠。其實Spring事務管理基於底層數據庫本身的事務處理機製。數據庫事務的基礎,是掌握Spring事務管理的基礎。這篇總結下數據庫事務。
一、數據庫事務
它的思想:we are 伐木累。就是多個SQL語句(一個團隊),要麼所有執行success,不然就fail。 它最終的目標:數據不會被破壞。即事務操作成功,數據的結果和業務期待的結果是一致的。這也就是ACID中的一致性(Consistency)。那什麼是ACID呢?
二、ACID
上麵是思想,牛人馬上根據思想建模,DBMS中數據庫事務滿足4各特性,即原子性、一致性、隔離性和持久性。下麵一一生動解釋: a)原子性 原子是物質的最小單元,即不可再分。 例如,以MySQL為例,每一個簡單的 SQL 語句即包含在一個事務中,具有原子性。這時候有人問了,那多個SQL呢?
BEGIN TRANSACTION; INSERT INTO `test`.`city` (`state`, `country`, `name`) VALUES ('1', 'China', 'CHINA','錯誤語句多了個VALUE'); INSERT INTO `test`.`city` (`state`, `country`, `name`) VALUES ('1', 'China', 'CHINA'); COMMIT;
結果:執行不通過。行3-5:為一個錯誤SQL。行6-8:是一個正確的SQL。它們各自被包裹在各自的隱式事務中,即Read Uncommited。T-all包裹了上麵具有原子性的T-1和T-2,實現了更大的原子,如下圖。b)一致性 終極目標:數據不會被破壞。(這不是廢話?確實有點)具體說,事務操作成功後,數據庫所處的狀態和它的業務規則是一致的,即數據不會被破壞。舉個栗子:兩句UPDATE語句,從A賬戶轉賬到B賬戶,不管成功失敗,A和B賬戶的總額是不變的。 c)隔離性 隔離:表示互不幹擾。事務與事務之間無法幹擾,即每個事務獨立,不會交叉。這樣可以讓多個線程並發訪問數據庫。如圖:
但是聰明的小夥伴知道,如果事務完全隔離,每次隻允許一個事務能訪問數據庫,那其他都是阻塞。會非常慢。 但是聰明的小夥伴也知道,這樣會造成數據的並發問題。(是的,在下麵第三節講)。 d)持久性 數據必須持久化到數據庫(存儲在磁盤)中。已提交的事務,即使在提交後數據庫崩潰,重啟數據庫時也能夠根據日誌對未持久化的數據進行重執行操作。(同學會問,那沒提交的事務呢?那就悲劇了(>﹏<)) 小結:數據的一致性是最終目標,其他特性都是其要求或手段。
三、隔離性中的問題:髒讀、不可重複讀和幻讀
對應上麵的隔離性,事務並發訪問的時候會出現:髒讀、不可重複讀和幻讀。案例轉自勇哥博客 髒讀:A事務讀取了B事務未提交的更改數據。一般數據庫事務默認不允許該問題出現。 比如這裏查詢應該是1500,現在出現了髒讀。
時間 | 事務 A(存款) | 事務 B(取款) |
T1 | 開始事務 | |
T2 | 開始事務 | |
T3 | 查詢餘額(1000 元) | |
T4 | 取出 1000 元(餘額 0 元) | |
T5 | 查詢餘額(0 元) | |
T6 | 撤銷事務(餘額恢複為 1000 元) | |
T7 | 存入 500 元(餘額 500 元) | |
T8 | 提交事務 |
不可重複讀:A事務讀取了B事務已提交的更改數據。 幻讀:A事務讀取了B事務提交的新增數據。 上麵的案例腦補吧,主要還是看下麵。 不可重複讀和幻讀區別:一個更改,一個新增數據。其實兩個區別在於一個是新增(insert語句),處理幻讀這個操作需要加表級別的鎖,將整個表鎖定,防止新增數據造成幻讀。另一個則是更改(update delete),這時候避免這個情況隻需要添加行級鎖組織該行發生變化即可。
四、事務隔離級別
既要求高的隔離性(安全性),又要求高並發性。這種是不可能的任務。根據各種鎖的操作機製出現了一個事務隔離級別。即相同情況下的輸入,不同隔離級別結果不同。為啥了,當然是在並發性和安全性的抉擇。如圖:按著圖說的,根據程序的並發性和安全性的抉擇。魚和熊掌不可兼得也~ 但分布式的時候,可以吧安全性關鍵的單獨分布式鎖。 好了,案例說了很多下麵代碼實戰。 ps: 休息下,泥瓦匠的代碼都會這github上~ ,這段代碼地址:https://github.com/JeffLi1993/jee-component-learning
五 、JDBC事務實戰
下麵利用MYSQL JDBC驅動連接MySQL,代碼如下:
public class TransactionLevels extends BaseJDBC { public static void main(String[] args) { try { // 加載數據庫驅動 Class.forName(DRIVER); // 數據庫連接 Connection conn = DriverManager.getConnection(URL,USER,PWD); // 數據庫元數據 DatabaseMetaData metaData = conn.getMetaData(); // 是否支持事務 boolean isSupport = metaData.supportsTransactions(); System.out.println(isSupport); // 是否支持的事務 boolean isSupportLevel = metaData.supportsTransactionIsolationLevel(Connection.TRANSACTION_SERIALIZABLE); System.out.println(isSupportLevel); // 獲取默認事務 int defaultIsolation = metaData.getDefaultTransactionIsolation(); System.out.println(defaultIsolation); /** 關閉數據庫連接 */ if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } catch (Exception e) { e.printStackTrace(); } } }
第 5、7行是連接數據庫 第 9 行:獲取數據庫元數據,這是包含數據庫連接信息 第 12 行:從元數據中,判斷是否支持事務 第 15 行:從元數據中,判斷是否支持事務級別 TRANSACTION_SERIALIZABLE 第 18 行:這裏可以看出MySQL默認支持的事務級別是 READ_COMMITTED,默認會隔離髒讀。 具體源碼如下:因此在安全性要求不高,支持高並發的情況下,選擇MySQL默認事務等級。但在安全性極高,幾乎不會出現高並發情況下,選擇更高的事務等級。根據上小節的圖一幕了然。
六、補充
關於事務,還有大家熟悉的Spring事務管理、具體數據庫事務的實現,推薦一本書《MySQL技術內幕InnoDB存儲引擎 》。 下一篇:ThreadLocal的工作機製,揭示Spring事務同步管理器的工作原理 如以上文章或鏈接對你有幫助的話,別忘了分享到朋友圈,讓更多的人閱讀這篇文章。
最後更新:2017-05-19 15:02:43