庫存-Mysql中的事務、鎖與存儲引擎

設計一個庫存系統。在庫存系統中,最重要的就是要防止超賣。模擬的SQL語句以下:
首先查詢是否有剩餘量,正常的操做爲:javascript

select  *  from t_goods where id=1 and  rest>0複製代碼

而後發現有剩餘量,開始執行更新操做:html

update t_goods   set rest=rest-1 where id=1;複製代碼

假設有A,B,C 三個用戶進來,那麼同時執行select語句,假設剩餘量只剩下1個,那麼A,B,C同時操做會引發超賣。java

那麼能夠在update的時候嚴格一下,也就是在update的時候再去判斷一次庫存是否大於0:mysql

update t_goods   set rest=rest-1 where id=1 and  rest > 0;複製代碼

那麼,一個線程在update的時候,另一個線程同時能訪問這條數據嗎?這看起來是一個簡單的問題,然而要清楚明白的瞭解發生了什麼,卻並不容易。
這裏就涉及到了數據庫的三大板塊:事務、,存儲引擎sql

先來講說什麼是事務,或者說把一個操做叫作事務,應該知足什麼特徵。數據庫

1、事務的4個基本特徵

  當事務處理系統建立事務時,將確保事務具備某些特性。組件的開發者們假設事務的特性應該是一些不須要他們親自管理的特性。這些特性稱爲ACID特性。 ACID就是:原子性(Atomicity )、一致性( Consistency )、隔離性或獨立性( Isolation)和持久性(Durabilily)。併發

一、原子性 (Atomicity )

   原子性屬性用於標識事務是否徹底地完成,一個事務的任何更新要在系統上徹底完成,若是因爲某種緣由出錯,事務不能完成它的所有任務,系統將返回到事務開始前的狀態。讓咱們再看一下銀行轉賬的例子。若是在轉賬的過程當中出現錯誤,整個事務將會回滾。只有當事務中的全部部分都成功執行了,纔將事務寫入磁盤並使變化 永久化。爲了提供回滾或者撤消未提交的變化的能力,許多數據源採用日誌機制。例如,SQL Server使用一個預寫事務日誌,在將數據應用於(或提交到)實際數據頁面前,先寫在事務日誌上。可是,其餘一些數據源不是關係型數據庫管理系統 (RDBMS),它們管理未提交事務的方式徹底不一樣。只要事務回滾時,數據源能夠撤消全部未提交的改變,那麼這種技術應該可用於管理事務。less

二、一致性( Consistency )

  事務在系統完整性中實施一致性,這經過保證系統的任何事務最後都處於有效狀態來實現。若是事務成功地完成,那麼系統中全部變化將正確地應用,系統處於有效狀態。若是在事務中出現錯誤,那麼系統中的全部變化將自動地回滾,系統返回到原始狀態。由於事務開
始時系統處於一致狀態,因此如今系統仍然處於一致狀態。 再讓咱們回頭看一下銀行轉賬的例子,在賬戶轉換和資金轉移前,賬戶處於有效狀態。若是事務成功地完成,而且提交事務,則賬戶處於新的有效的狀態。若是事務出錯,終止後,賬戶返回到原先的有效狀態。
記住,事務不負責實施數據完整性,而僅僅負責在事務提交或終止之後確保數據返回到一致狀態。理解數據完整性規則並寫代碼實現完整性的重任一般落在 開發者肩上,他們根據業務要求進行設計。 當許多用戶同時使用和修改一樣的數據時,事務必須保持其數據的完整性和一致性。所以咱們進一步研究ACID特性中的下一個特性:隔離性。ide

三、隔離性 ( Isolation)

  隔離性是當多個用戶併發訪問數據庫時,好比操做同一張表時,數據庫爲每個用戶開啓的事務,不能被其餘事務的操做所幹擾,多個併發事務之間要相互隔離。即要達到這麼一種效果:對於任意兩個併發的事務T1和T2,在事務T1看來,T2要麼在T1開始以前就已經結束,要麼在T1結束以後纔開始,這樣每一個事務都感受不到有其餘事務在併發地執行。事務的隔離性經過使用鎖定來實現。性能

四、持久性 (Durabilily)

  持久性意味着一旦事務執行成功,在系統中產生的全部變化將是永久的。應該存在一些檢查點防止在系統失敗時丟失信息。甚至硬件自己失敗,系統的狀態仍能經過在日誌中記錄事務完成的任務進行重建。持久性的概念容許開發者認爲無論系統之後發生了什麼變化,完 成的事務是系統永久的部分。

  對於數據庫來講,一、二、4特性較容易知足。數據庫是一個共享資源,能夠同時供多個用戶使用。也就是須要對事務進行併發的控制,以知足隔離性。若是一個個事務是串行的執行,也就是一次只能執行一個事務,只有一個事務等到另一個事務徹底提交修改之後再執行另一個事務,那麼徹底知足了隔離性。可是此時數據庫系統大部分都是處於空閒狀態,其效率將會很低。所以,事務應該容許併發的執行,而且應該對事務進行併發控制。若是不對事務進行併發控制,會出現如下三種狀況:

一、丟失更新(Lost update)

  兩個事務都同時更新一行數據,可是第二個事務卻覆蓋了第一個事務的修改。好比T1,T2事務都發現當前剩餘量爲16,而後減1, T1修改之後爲15,T2修改之後一樣爲15,T2的修改覆蓋了T1。

二、非重複讀(Non-repeatable Reads)

  一個事務對同一行數據重複讀取兩次,可是卻獲得了不一樣的結果。同一查詢在同一事務中屢次進行,因爲其餘提交事務所作的修改或刪除,每次返回不一樣的結果集,此時發生非重複讀。 更爲通俗的說法是:在一個事務內,屢次讀同一個數據。在這個事務尚未結束時,另外一個事務也訪問該同一數據。那麼,在第一個事務的兩次讀數據之間。因爲第二個事務的修改,那麼第一個事務讀到的數據可能不同,這樣就發生了在一個事務內兩次讀到的數據是不同的,所以稱爲不可重複讀,即原始讀取不可重複。若是同一個事務在第二次讀取的時候發現這條記錄消失了或者增長了,這種狀況叫作幻讀。

三、髒讀(Dirty Reads)

   一個事務開始讀取了某行數據,可是另一個事務已經更新了此數據但沒有可以及時提交或者回滾,那麼這個事務有可能讀取的是修改前的值,致使了髒讀。髒讀和丟失更新的惟一區別就在於丟失更新所讀取的數據是正確的。

  出現這樣的緣由是事務的隔離性遭到了破壞。可是在某些狀況下,併發所形成的事務問題並非徹底不可接受的,好比有可能出現幻讀。所以,在性能與事務特性的平衡選擇下,SQL給出了4種隔離級別。SQL標準定義了4類隔離級別,包括了一些具體規則,用來限定事務內外的哪些改變是可見的,哪些是不可見的。低級別的隔離級通常支持更高的併發處理,並擁有更低的系統開銷。

1.Read Uncommitted(讀取未提交內容)

  在該隔離級別,全部事務均可以看到其餘未提交事務的執行結果。本隔離級別不多用於實際應用,由於它的性能也不比其餘級別好多少。讀取未提交的數據,也被稱之爲髒讀(Dirty Read)。

2.Read Committed(讀取提交內容)

   這是大多數數據庫系統的默認隔離級別(但不是MySQL默認的)。它知足了隔離的簡單定義:一個事務只能看見已經提交事務所作的改變。這種隔離級別 也支持所謂的不可重複讀(Nonrepeatable Read),由於同一事務的其餘實例在該實例處理其間可能會有新的commit,因此同一select可能返回不一樣結果。

3.Repeatable Read(可重讀)

  這是MySQL的默認事務隔離級別,它確保同一事務的多個實例在併發讀取數據時,會看到一樣的數據行。不過理論上,這會致使另外一個棘手的問題:幻讀 (Phantom Read)。簡單的說,幻讀指當用戶讀取某一範圍的數據行時,另外一個事務又在該範圍內插入了新行,當用戶再讀取該範圍的數據行時,會發現有新的「幻影」 行。InnoDB和Falcon存儲引擎經過多版本併發控制(MVCC,Multiversion Concurrency Control)機制解決了該問題。

4.Serializable(可串行化)

  這是最高的隔離級別,它經過強制事務排序,使之不可能相互衝突,從而解決幻讀問題。簡言之,它是在每一個讀的數據行上加上共享鎖。在這個級別,可能致使大量的超時現象和鎖競爭。

使用select @@tx_isolation;能夠查看查看Mysql的默認的事務隔離級別:

mysql>    select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 rows in set (0.02 sec)

mysql>複製代碼

  Mysql使用鎖來完成併發控制。要特別說明的是,對於原子性,一致性和持久性來講,用戶沒法介入,可是對於併發控制,用戶卻能手工的靈活操縱,也就是說,數據庫事務的隔離性有時候須要用戶手工控制。
基本的封鎖類型有三種種:排它鎖(X鎖)和共享鎖(S鎖)已經意向鎖。

X鎖

  所謂X鎖,是事務T對數據A加上X鎖時,只容許事務T讀取和修改數據A,其餘事務不能讀取也不能修改數據庫A。

S鎖

  所謂S鎖,是事務T對數據A加上S鎖時,其餘事務只能再對數據A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。若事務T對數據對象A加了S鎖,則T就能夠對A進行讀取,但不能進行更新(S鎖所以又稱爲讀鎖),在T釋放A上的S鎖之前,其餘事務能夠再對A加S鎖,但不能加X鎖,從而能夠讀取A,但不能更新A。

意向鎖:

  多粒度封鎖協議容許多粒樹中的每一個結點被獨立加鎖。對一個結點加鎖意味着這個結點的全部後裔結點也被加以一樣類型的鎖。顯示封鎖是數據庫直接對數據對象加鎖,隱式封鎖是該對象沒有被獨立封鎖,而是其上級對象被封鎖而致使該數據對象被加鎖。所以系統在對某一數據對象加鎖時不只要檢查該數據對象上有無(顯式和隱式)封鎖與之衝突;還要檢查其全部上級結點和全部下級結點,看申請的封鎖是否與這些結點上的(顯式和隱式)封鎖衝突;顯然,這樣的檢查方法效率很低。爲此引進了意向鎖。意向鎖的含義是:對任一結點加鎖時,必須先對它的上層結點加意向鎖。

   例如事務T要對某個元組加X鎖,則首先要對關係和數據庫加IX鎖。換言之,對關係和數據庫加IX鎖,表示它的後裔結點—某個元組擬(意向)加X鎖。

   引進意向鎖後,系統對某一數據對象加鎖時沒必要逐個檢查與下一級結點的封鎖衝突了。例如,事務T要對關係R加X鎖時,系統只要檢查根結點數據庫和R自己是否已加了不相容的鎖(如發現已經加了IX,則與X衝突),而再也不須要搜索和檢查R中的每個元組是否加了X鎖或S鎖。

鎖定時間的長短

  鎖保持的時間長度爲保護所請求級別上的資源所需的時間長度。用於保護讀取操做的共享鎖的保持時間取決於事務隔離級別。採用 READ COMMITTED 的事務隔離級別時,只在讀取頁的期間內控制共享鎖。在掃描中,直到在掃描內的下一頁上獲取鎖時才釋放鎖。若是指定 HOLDLOCK 提示或者將事務隔離級別設置爲 REPEATABLE READSERIALIZABLE,則直到事務結束才釋放鎖。

  根據爲遊標設置的併發選項,遊標能夠獲取共享模式的滾動鎖以保護提取。當須要滾動鎖時,直到下一次提取或關閉遊標(以先發生者爲準)時才釋放滾動鎖。可是,若是指定 HOLDLOCK,則直到事務結束才釋放滾動鎖。

  請注意用於保護更新的排它鎖(X鎖)將直到事務結束才釋放。

  若是一個鏈接試圖獲取一個鎖,而該鎖與另外一個鏈接所控制的鎖衝突,則試圖獲取鎖的鏈接將一直阻塞到將衝突鎖釋放並且鏈接獲取了所請求的鎖或者鏈接的超時間隔已到期。默認狀況下沒有超時間隔,可是一些應用程序設置超時間隔以防止無限期等待。

存儲引擎

  並非全部的存儲引擎都支持事務和鎖,使用show engines查看各個存儲引擎對事務和鎖的概況:

mysql> show engines;
+------------+---------+------------------------------------------------------------+--------------+------+------------+
| Engine     | Support | Comment                                                    | Transactions | XA   | Savepoints |
+------------+---------+------------------------------------------------------------+--------------+------+------------+
| MRG_MYISAM | YES     | Collection of identical MyISAM tables                      | NO           | NO   | NO         |
| CSV        | YES     | CSV storage engine                                         | NO           | NO   | NO         |
| MyISAM     | DEFAULT | Default engine as of MySQL 3.23 with great performance     | NO           | NO   | NO         |
| InnoDB     | YES     | Supports transactions, row-level locking, and foreign keys | YES          | YES  | YES        |
| MEMORY     | YES     | Hash based, stored in memory, useful for temporary tables  | NO           | NO   | NO         |
+------------+---------+------------------------------------------------------------+--------------+------+------------+
5 rows in set (0.02 sec)複製代碼

  能夠發現,只有InnoDB才徹底支持事務,行級鎖和外鍵。MyISAM引擎不支持事務。若是要使用事務,必須是innodb引擎:alter table table_name engine=innodb;

分析Mysql事務中鎖的變化

  假設有以下三條語句組成的事務

//1.查詢出商品信息

select status from t_goods where id=1;

//2.根據商品信息生成訂單

insert into t_orders (id,goods_id) values (null,1);

//3.修改商品status爲2

update t_goods set status=2;複製代碼

  事務採用默認的MySQL默認的Repeatable Read隔離級別。若是要有關各類sql語句到底加什麼鎖的信息能夠訪問[dev.mysql.com/doc/refman/…]

select status from t_goods where id=1;   沒有加鎖,當前事務讀,其餘事務也能夠讀
insert 加排它鎖,但不影響其餘事務的插入其餘記錄。(排它鎖,行級鎖)
update 加排它鎖,影響其餘事務讀這條記錄(排它鎖,行級鎖)複製代碼

這裏只列舉了Mysql手冊中關於SELECT ... FROM 中鎖的描述:
>
SELECT ... FROM is a consistent read, reading a snapshot of the database and setting no locks unless the transaction isolation level is set to SERIALIZABLE. For SERIALIZABLE level, the search sets shared next-key locks on the index records it encounters. However, only an index record lock is required for statements that lock rows using a unique index to search for a unique row.

能夠看到,默認並無加鎖,那麼有可能出現不可重複讀的狀況。只有在SERIALIZABLE的級別下,纔會加shared next-key locks。

  用戶能夠爲select手工的加上一些鎖,以防止其餘事務訪問。好比select for update

//1.查詢出商品信息

select status from t_goods where id=1 for update;

//2.根據商品信息生成訂單

insert into t_orders (id,goods_id) values (null,1);

//3.修改商品status爲2

update t_goods set status=2;複製代碼

  上面咱們提到,使用select…for update會把數據給鎖住,加上一個排它鎖。不過咱們須要注意一些鎖的級別,MySQL InnoDB默認Row-Level Lock,因此只有「明確」地指定主鍵,MySQL 纔會執行Row lock (只鎖住被選取的數據) ,不然MySQL 將會執行Table Lock (將整個數據表單給鎖住)。select…for update 的排它鎖,只有等到事務結束才釋放。
有關select for update的更多信息,能夠訪問:dev.mysql.com/doc/refman/… 瞭解更多。

事務邊界

  Mysql默認自動提交事務,所以一條SQL語句就是一個事務。可使用show variables like 'autocommit'查看事務自動提交狀態:

show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 rows in set (0.03 sec)複製代碼

  若是一個事務要包含多條事務,那麼必須顯示指定事務的邊界。好比使用 begincommit關鍵字。

Mysql中的鎖

排它鎖,讀鎖和意向鎖只是從鎖的做用來劃分。從鎖的粒度來劃分,Mysql鎖的類型能夠從dev.mysql.com/doc/refman/… 瞭解到。請注意,行級鎖,表級鎖,頁級鎖只是一種級別劃分。並不存在這樣的鎖的名字叫作「行級鎖,表級鎖,頁級鎖」。

查看錶的基本狀態(存儲引擎類型,索引數等):

SHOW TABLE STATUS FROM database_name;

JDBC中的事務與Mysql中的事務

從JDBC的角度來講,提交事務就是提交sql給數據庫來執行。由數據庫來保證最後的事務處理。JDBC的事務其實就是保證這些事務之間的數據要麼所有交給數據庫執行,要麼所有不交給數據庫。若是設置了自動提交,那麼sql一旦出現,就當即執行。 MYSQL默認是自動提交的,也就是你提交一個QUERY,它就直接執行!咱們能夠經過 set autocommit=0 禁止自動提交 set autocommit=1 開啓自動提交因此,在JDBC中,一塊兒提交sql語句才起做用。(JDBC會給數據庫增長事務處理語句,而後再發給數據庫)所以,mysql是否開啓自動提交關係不大。若是事務不是由jdbc來生成的,那麼提交一系列的sql語句到數據庫之後就被數據庫當作單條數據庫執行了,那麼就沒有意義了。

相關文章
相關標籤/搜索