mysql數據庫隔離級別及其原理、Spring的7種事物傳播行爲

1、事務的基本要素(ACID)html

  一、原子性(Atomicity):事務開始後全部操做,要麼所有作完,要麼所有不作,不可能停滯在中間環節。事務執行過程當中出錯,會回滾到事務開始前的狀態,全部的操做就像沒有發生同樣。也就是說事務是一個不可分割的總體,就像化學中學過的原子,是物質構成的基本單位。mysql

   二、一致性(Consistency):事務開始前和結束後,數據庫的完整性約束沒有被破壞 。好比A向B轉帳,不可能A扣了錢,B卻沒收到。
算法

 

   三、隔離性(Isolation):同一時間,只容許一個事務請求同一數據,不一樣的事務之間彼此沒有任何干擾。好比A正在從一張銀行卡中取錢,在A取錢的過程結束前,B不能向這張卡轉帳。sql

 

   四、持久性(Durability):事務完成後,事務對數據庫的全部更新將被保存到數據庫,不能回滾。數據庫

 

2、事務的併發問題編程

  一、髒讀:一個事務讀取另一個事務尚未提交的數據叫髒讀【針對未提交的數據segmentfault

  二、不可重複讀:即在同一個事務內,兩個相同的查詢返回了不一樣的結果【讀取數據自己的對比                併發

    表tms_message_info數據:
ide

      ID    file_name性能

                1     1111.SND

                 2    2222.SND

 

 

事務A READ-COMMITTED 事務B READ-COMMITTED

start transaction;

select * from tms_message_info where id='1'

start transaction;-- 開啓事務

update tms_message_info set FILE_NAME='666.SND' where ID='1'; 未提交

B事務未提交前,A事務執行上述查詢,獲得的文件名結果爲1111.SND commit;  B 事務提交
B事務提交後,A事務在執行查詢,結果發生變化,文件名變爲666.SND,即同一事務查詢結果不一樣,即不可重複讀  

 

  三、幻讀:系統管理員A將數據庫中全部學生的成績從具體分數改成ABCDE等級,可是系統管理員B就在這個時候插入了一條具體分數的記錄,當系統管理員A改結束後發現還有一條記錄沒有改過來,就好像發生了幻覺同樣,這就叫幻讀。

  【同一事務A屢次查詢,若另外一事務B只是update,則A事務屢次查詢結果相同;若B事務insert/delete數據,則A事務屢次查詢就會發現新增或缺乏數據,出現幻讀,即幻讀關注讀取結果集條數變化

  小結:不可重複讀的和幻讀很容易混淆,不可重複讀側重於修改,幻讀側重於新增或刪除。解決不可重複讀的問題只需鎖住知足條件的行,解決幻讀須要鎖表

 

3、MySQL事務隔離級別

 

一、讀不提交(Read Uncommited,RU)

 

  這種隔離級別下,事務間徹底不隔離,會產生髒讀,能夠讀取未提交的記錄,實際狀況下不會使用。

 

二、讀提交(Read commited,RC)

 

  本事務讀取到的是最新的數據(其餘事務提交後的)。問題是,在同一個事務裏,先後兩次相同的SELECT會讀到不一樣的結果(不重複讀)

 

三、可重複讀(Repeatable Read,RR)【MySQL 默認的級別】

 

  在同一個事務裏,SELECT的結果是事務開始時時間點的狀態,所以,同一個事務一樣的SELECT操做讀到的結果會是一致的。可是,會有幻讀現象

 

四、 串行化(SERIALIZABLE)。讀操做會隱式獲取共享鎖,能夠保證不一樣事務間的互斥

 

 

 

 

4、4種隔離級別的相應原理總結以下:

READ_UNCOMMITED 的原理:

  • 事務對當前被讀取的數據不加鎖;
  • 事務在更新某數據的瞬間(就是發生更新的瞬間),必須先對其加 行級共享鎖,直到事務結束才釋放。

表現:

  • 事務1讀取某行記錄時,事務2也能對這行記錄進行讀取、更新;當事務2對該記錄進行更新時,事務1再次讀取該記錄,能讀到事務2對該記錄的修改版本,即便該修改還沒有被提交。
  • 事務1更新某行記錄時,事務2不能對這行記錄作更新,直到事務1結束。

READ_COMMITED 的原理:

  • 事務對當前被讀取的數據加 行級共享鎖(當讀到時才加鎖),一旦讀完該行,當即釋放該行級共享鎖;
  • 事務在更新某數據的瞬間(就是發生更新的瞬間),必須先對其加 行級排他鎖,直到事務結束才釋放。

表現:

  • 事務1讀取某行記錄時,事務2也能對這行記錄進行讀取、更新;當事務2對該記錄進行更新時,事務1再次讀取該記錄,讀到的只能是事務2對其更新前的版本,要不就是事務2提交後的版本。
  • 事務1更新某行記錄時,事務2不能對這行記錄作更新,直到事務1結束。

REPEATABLE READ 的原理:

  • 事務在讀取某數據的瞬間(就是開始讀取的瞬間),必須先對其加 行級共享鎖,直到事務結束才釋放;
  • 事務在更新某數據的瞬間(就是發生更新的瞬間),必須先對其加 行級排他鎖,直到事務結束才釋放。

表現:

  • 事務1讀取某行記錄時,事務2也能對這行記錄進行讀取、更新;當事務2對該記錄進行更新時,事務1再次讀取該記錄,讀到的仍然是第一次讀取的那個版本。
  • 事務1更新某行記錄時,事務2不能對這行記錄作更新,直到事務1結束。

SERIALIZABLE 的原理:

  • 事務在讀取數據時,必須先對其加 表級共享鎖 ,直到事務結束才釋放;
  • 事務在更新數據時,必須先對其加 表級排他鎖 ,直到事務結束才釋放。

表現:

  • 事務1正在讀取A表中的記錄時,則事務2也能讀取A表,但不能對A表作更新、新增、刪除,直到事務1結束。
  • 事務1正在更新A表中的記錄時,則事務2不能讀取A表的任意記錄,更不可能對A表作更新、新增、刪除,直到事務1結束。

 

註釋:

一、共享鎖:稱爲讀鎖,簡稱S鎖,顧名思義,共享鎖就是若是事務T對數據A加上共享鎖後,則其餘事務只能對A再加共享鎖,不能加排他鎖。獲准共享鎖的事務只能讀數據,不能修改數據。共享鎖也屬於悲觀鎖的一種

 

ps:mysql中 select * from xx where id =1 lock in share mode便可給該數據加共享鎖,在該事務提交以前,不容許其餘事務進行修改。
 

 

二、排他鎖:又稱爲寫鎖,簡稱X鎖,顧名思義,排他鎖就是不能與其餘所並存,如一個事務獲取了一個數據行的排他鎖,其餘事務就不能給該數據加其餘鎖,包括共享鎖和排他鎖,可是獲取排他鎖的事務是能夠對數據就行讀取和修改。

ps:對於update,insert,delete語句會自動加排它鎖)。添加排它鎖與添加共享鎖相似,執行語句添加 「 for update」 便可。

 

對於共享鎖你們可能很好理解,就是多個事務只能讀數據不能改數據,對於排他鎖你們的理解可能就有些差異,我當初就犯了一個錯誤,覺得排他鎖鎖住一行數據後,其餘事務就不能讀取和修改該行數據,其實不是這樣的。排他鎖指的是一個事務在一行數據加上排他鎖後,其餘事務不能再在其上加其餘的鎖。mysql InnoDB引擎默認的修改數據語句,update,delete,insert都會自動給涉及到的數據加上排他鎖,select語句默認不會加任何鎖類型,若是加排他鎖可使用select ...for update語句,加共享鎖可使用select ... lock in share mode語句。因此加過排他鎖的數據行在其餘事務種是不能修改數據的,也不能經過for update和lock in share mode鎖的方式查詢數據,但能夠直接經過select ...from...查詢數據,由於普通查詢沒有任何鎖機制。

 

 

悲觀鎖:老是假設最壞的狀況,每次去拿數據的時候都認爲別人會修改,因此每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖(共享資源每次只給一個線程使用,其它線程阻塞,用完後再把資源轉讓給其它線程)。傳統的關係型數據庫裏邊就用到了不少這種鎖機制,好比行鎖,表鎖等,讀鎖,寫鎖等,都是在作操做以前先上鎖。Java中同步鎖synchronized和重入鎖ReentrantLock等獨佔鎖就是悲觀鎖思想的實現。
共享鎖和排他鎖也是悲觀鎖

(另外:同步鎖synchronized和重入鎖ReentrantLock的相同點與不一樣點:

    相同點:1)ReentrantLock和synchronized都是獨佔鎖,只容許線程互斥的訪問臨界區

        2)ReentrantLock和synchronized都是可重入的

    不一樣點:1)ReentrantLock能夠實現公平鎖。公平鎖是指當鎖可用時,在鎖上等待時間最長的線程將得到鎖的使用權。而非公平鎖則隨機分配這種使用權。默認狀況下兩者都是非公平鎖,可是ReentrantLock能夠設置爲公平鎖

        2)ReentrantLock可響應中斷。當使用synchronized實現鎖時,阻塞在鎖上的線程除非得到鎖不然將一直等待下去,也就是說這種無限等待獲取鎖的行爲沒法被中斷。而ReentrantLock給咱們提供了一個能夠響應中斷的獲取鎖的方法 

          lockInterruptibly()。該方法能夠用來解決死鎖問題。

        3)獲取鎖時限時等待。ReentrantLock還給咱們提供了獲取鎖限時等待的方法tryLock(),能夠選擇傳入時間參數,表示等待指定的時間,無參則表示當即返回鎖申請的結果:true表示獲取鎖成功,false表示獲取鎖失敗。咱們可使用該方法配合失敗重試機制         來更好的解決死鎖問題。

        4)結合Condition實現等待通知機制。    

        

Condition接口在使用前必須先調用ReentrantLock的lock()方法得到鎖。以後調用Condition接口的await()將釋放鎖,而且在該Condition上等待,直到有其餘線程調用Condition的signal()方法喚醒線程。使用方式和wait,notify相似

public class ConditionTest {

    static ReentrantLock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();
    public static void main(String[] args) throws InterruptedException {

        lock.lock();
        new Thread(new SignalThread()).start();
        System.out.println("主線程等待通知");
        try {
            condition.await();
        } finally {
            lock.unlock();
        }
        System.out.println("主線程恢復運行");
    }
    static class SignalThread implements Runnable {

        @Override
        public void run() {
            lock.lock();
            try {
                condition.signal();
                System.out.println("子線程通知");
            } finally {
                lock.unlock();
            }
        }
    }
}

//執行結果
主線程等待通知
子線程通知
主線程恢復運行
View Code

 

樂觀鎖:所謂樂觀鎖就是,每次不加鎖而是假設沒有衝突而去完成某項操做,若是由於衝突失敗就重試,直到成功爲止。

方法1:加版本號

1.查詢出商品信息
select (status,status,version) from t_goods where id=#{id}
2.根據商品信息生成訂單
3.修改商品status爲2
update t_goods 
set status=2,version=version+1
where id=#{id} and version=#{version};

 方法2:cas算法(即比較與交換):一種無鎖算法

CAS算法涉及到三個操做數

    須要讀寫的內存值 V
    進行比較的值 A
    擬寫入的新值 B

當且僅當 V 的值等於 A時,CAS經過原子方式用新值B來更新V的值,不然不會執行任何操做(比較和替換是一個原子操做)。通常狀況下是一個自旋操做,即不斷的重試。
public final int getAndIncrement() {
   for (;;) {
    int current = get();
    int next = current + 1;
    if (compareAndSet(current, next))
    return current;
  }
}

樂觀鎖缺點:1)ABA問題:若是一個變量V初次讀取的時候是A值,而且在準備賦值的時候檢查到它仍然是A值,那咱們就能說明它的值沒有被其餘線程修改過了嗎?很明顯是不能的,由於在這段時間它的值可能被改成其餘值,而後又改回A,那CAS操做就會誤認爲它歷來沒有被修改過。這個問題被稱爲CAS操做的 "ABA"問題。

      2)自旋CAS(也就是不成功就一直循環執行直到成功)若是長時間不成功,會給CPU帶來很是大的執行開銷

       3)只能保證一個共享變量的原子操做

另外:CAS(樂觀鎖)與synchronized(悲觀鎖)的使用情景

簡單的來講CAS適用於寫比較少的狀況下(多讀場景,衝突通常較少),synchronized適用於寫比較多的狀況下(多寫場景,衝突通常較多)

1)對於資源競爭較少(線程衝突較輕)的狀況,使用synchronized同步鎖進行線程阻塞和喚醒切換以及用戶態內核態間的切換操做額外浪費消耗cpu資源;而CAS基於硬件實現,不須要進入內核,不須要切換線程,操做自旋概率較少,所以能夠得到更高的性能。
2)對於資源競爭嚴重(線程衝突嚴重)的狀況,CAS自旋的機率會比較大,從而浪費更多的CPU資源,效率低於synchronized。

 

 

 

注意:mysql的update、delete、insert是默認加了排他鎖的,而普通的selete是什麼鎖都沒加,因此普通selete既能查共享鎖的行也能查排他鎖的行

參考:https://www.cnblogs.com/lyftest/p/7687747.html,https://www.cnblogs.com/lyftest/p/7687747.html

https://blog.csdn.net/weixin_39666581/article/details/87008846

https://blog.csdn.net/u013030086/article/details/86492335

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Spring的7種事物傳播行爲:

參考:https://segmentfault.com/a/1190000013341344

    Spring有7中事物傳播行爲:由@Transaction(Propagation=XXX)設置決定:
    1)PROPAGATION_REQUIRED:若是當前沒有事務,就新建一個事務,若是已經存在一個事務中,加入到這個事務中。這是最多見的選擇。即:無論內部事物仍是外部事物,只要發生異常,所有回滾
    2)PROPAGATION_REQUIRES_NEW:新建事務,若是當前存在事務,把當前事務掛起。
    3)PROPAGATION_SUPPORTS:支持當前事物,若是沒有事物,就以非事物方式執行。
    4)PROPAGATION_MANDATORY:使用當前事物,沒有就拋出異常
    5)PROPAGATION_NOT_SUPPORTED:使用非事物的方式執行,若是當前存在事物,就把當前事物掛起
    6)PROPAGATION_NEVER:以非事物方式執行,若是當前事物存在,則拋出異常
    7)PROPAGATION_NESTED:若是當前存在事物,則嵌套事物內部執行;若是當前沒有事物,則執行與PROPAGATION_REQUIRED相似操做
    https://segmentfault.com/a/1190000013341344

    REQUIRED,REQUIRES_NEW,NESTED異同
  NESTEDREQUIRED修飾的內部方法都屬於外圍方法事務,若是外圍方法拋出異常,這兩種方法的事務都會被回滾。可是REQUIRED是加入外圍方法事務,因此和外圍事務同屬於一個事務,一旦REQUIRED事務拋出異常被回滾,外圍方法事務也將被回滾。而NESTED是外圍方法的子事務,有單獨的保存點,因此NESTED方法拋出異常被回滾,不會影響到外圍方法的事務。

  NESTEDREQUIRES_NEW均可以作到內部方法事務回滾而不影響外圍方法事務。可是由於NESTED是嵌套事務,因此外圍方法回滾以後,做爲外圍方法事務的子事務也會被回滾。而REQUIRES_NEW是經過開啓新的事務實現的,內部事務和外圍事務是兩個事務,外圍事務回滾不會影響內部事務。

 

事務能夠分爲編程式事務和聲明式事務。

關於聲明式事物@Transactional兩個注意事項

一、在Service層拋出Exception,在Controller層捕獲,那若是在Service中有異常,那會事務回滾嗎?

結論:若是是編譯時異常不會自動回滾,若是是運行時異常,那會自動回滾

二、若是我在當前類下使用一個沒有事務的方法去調用一個有事務的方法,那咱們此次調用會怎麼樣?是否會有事務呢?

結論:若是是在同一個service中(即在同一個@Service下),一個沒有事物的方法調用一個有事物的方法,那麼事物不會生效;若是再也不同一個service中(即在不一樣的@Service),則會生效。

緣由:

咱們都知道,帶有@Transactional註解所包圍的方法就能被Spring事務管理起來,那若是我在當前類下使用一個沒有事務的方法去調用一個有事務的方法,那咱們此次調用會怎麼樣?是否會有事務呢?

用代碼來描述一下:

// 沒有事務的方法去調用有事務的方法
public Employee addEmployee2Controller() throws Exception {

    return this.addEmployee();
}

@Transactional
public Employee addEmployee() throws Exception {

    employeeRepository.deleteAll();
    Employee employee = new Employee("3y", 23);

    // 模擬異常
    int i = 1 / 0;

    return employee;
}

 

我第一直覺是:這跟Spring事務的傳播機制有關吧。

其實這跟Spring事務的傳播機制沒有關係,下面我講述一下:

  • Spring事務管理用的是AOP,AOP底層用的是動態代理。因此若是咱們在類或者方法上標註註解@Transactional,那麼會生成一個代理對象

接下來我用圖來講明一下:

 

 顯然地,咱們拿到的是代理(Proxy)對象,調用addEmployee2Controller()方法,而addEmployee2Controller()方法的邏輯是target.addEmployee(),調用回原始對象(target)的addEmployee()。因此此次的調用壓根就沒有事務存在,更談不上說Spring事務傳播機制了。

 從上面的測試咱們能夠發現:若是是在本類中沒有事務的方法來調用標註註解@Transactional方法,最後的結論是沒有事務的。那若是我將這個標註註解的方法移到別的Service對象上,有沒有事務?

@Service
public class TestService {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Transactional
    public Employee addEmployee() throws Exception {

        employeeRepository.deleteAll();

        Employee employee = new Employee("3y", 23);

        // 模擬異常
        int i = 1 / 0;

        return employee;
    }

}


@Service
public class EmployeeService {

    @Autowired
    private TestService testService;
    // 沒有事務的方法去調用別的類有事務的方法
    public Employee addEmployee2Controller() throws Exception {
        return testService.addEmployee();
    }
}

 

這時就會有事務了。

由於咱們用的是代理對象(Proxy)去調用addEmployee()方法,那就固然有事務了。

相關文章
相關標籤/搜索