關於資料庫事務併發的理解和處理

Aaron_alive發表於2019-02-18

關於資料庫事務併發的理解和處理

  • 併發的概念:在作業系統中,併發是指一個時間段中有幾個程式都處於已啟動執行到執行完畢之間,且這幾個程式都是在同一個處理機上執行,但任一個時刻點上只有一個程式在處理機上執行。

    在關聯式資料庫中,允許多個使用者同時訪問和更改共享資料的程式。

  • 理解事務的概念

    1. 概念:

      MySQL 事務主要用於處理操作量大,複雜度高的資料, 比如說,在人員管理系統中,你刪除一個人員,你即需要刪除人員的基本資料,也要刪除和該人員相關的資訊,如信箱,文章等等,這樣,這些資料庫操作語句就構成一個事務!

    2. 事務的四個重要特徵

      • 原子性

        一個事務(transaction)中的所有操作,要麼全部完成,要麼全部失敗,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。也就是說事務執行成功全部會應用到資料庫,失敗的話對資料庫沒用任何影響。

      • 一致性

        在事務開始之前和事務結束以後,資料庫的完整性沒有被破壞。比如說A賬戶有1000塊,B賬戶有100塊,A轉賬500給B,那麼A變成500,B變成600,也就是說兩者之間無論如何轉賬都好,在事務結束之後加起來都是1100,這就是一致性。

      • 隔離性 : (這一點很重要,直接相關事務併發)

        資料庫允許多個併發事務同時對其資料進行讀寫和修改的能力,隔離性可以防止多個事務併發執行時由於交叉執行而導致資料的不一致。事務隔離分為不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和序列化(Serializable)。

      • 永續性

        事務處理結束後,對資料的修改就是永久的,即便系統故障也不會丟失。

    3. 重點講解事務的隔離性

      隔離性是如何防止多個事務併發執行時由於交叉執行而導致資料的不一致的安全問題的呢?答案是通過設定隔離級別來解決的。

      • mysql四種隔離級別
      隔離級別 髒讀 不可重複讀 幻讀
      讀未提交(read-uncommitted)
      不可重複讀(read-committed) ×
      可重複讀(repeatable-read) × ×
      序列化(serializable) × × ×
    • 其中需要注意的是:

      • 可重複讀的隔離級別下使用了MVCC機制,select操作不會更新版本號,是快照讀(歷史版本);insert、update和delete會更新版本號,是當前讀(當前版本)。所以這也會帶來“髒寫”問題

      • mysql中事務隔離級別為serializable時會鎖表,因此不會出現幻讀的情況,這種隔離級別併發性極低,開發中很少會用到。

    • 在mysql中檢視當前隔離事務級別語句:select @@tx_isolation;

    • 在mysql中設定事務的隔離級別語句: set tx_isolation='隔離級別名稱';

      那麼既然可以通過設定隔離級別為序列來解決併發時帶來的資料不一致問題,那為什麼不直接把資料庫隔離級別改為serializable就好?的確,這樣的確可以解決資料在多事務併發處理下資料不一致問題,但是往往帶來的是更大的效能開銷,這些效能的開銷往往是大於結合事務隔離級別和其它併發機制來處理事務的併發的開銷的。正確性和效率不可兼得,很多小公司在涉及到錢的業務程式碼上都會啟用事務把,但是其實是隻有小數量的公司才會遇上高併發的情況,多數都只是處理好併發就好了,那麼問題來了,如果只是處理很小量的併發,而且是時有時沒有的時候,那就應該把級別改為序列?並不是的,改了是整個資料庫受到影響的,包括所有的表,所以其他的查詢和更改都會上鎖了,效率是極其低下的。

  • 資料庫併發控制解決方案

    鎖的分類

    • 從資料庫系統角度分為三種:排他鎖(x)、共享鎖(s)、更新鎖(u)。

    • 從程式設計師角度分為兩種:一種是悲觀鎖,一種樂觀鎖。

    這裡只討論悲觀鎖樂觀鎖

    • 悲觀鎖:

      需要資料庫本身的支援,通過SQL語句“select for update”鎖住select出的那批資料,總是假設最壞的情況,每次取資料時都認為其他執行緒會修改,當其他執行緒想要訪問資料時,都需要阻塞掛起。悲觀併發控制實際上是“先取鎖再訪問”的保守策略,為資料處理的安全提供了保證。但是在效率方面,處理加鎖的機制會讓資料庫產生額外的開銷,還有增加產生死鎖的機會;另外,在只讀型事務處理中由於不會產生衝突,也沒必要使用鎖,這樣做只能增加系統負載;還有會降低了並行性,一個事務如果鎖定了某行資料,其他事務就必須等待該事務處理完才可以處理那行資料。

    • 樂觀鎖

      樂觀鎖,雖然名字中帶“鎖”,但是樂觀鎖並不鎖住任何東西,而是在提交事務時檢查這條記錄是否被其他事務進行了修改:如果沒有,則提交;否則,進行回滾。相對於悲觀鎖,在對資料庫進行處理的時候,樂觀鎖並不會使用資料庫提供的鎖機制。如果併發的可能性並不大,那麼樂觀鎖定策略帶來的效能消耗是非常小的。樂觀鎖採用的實現方式一般是記錄資料版本。

      資料版本是為資料增加的一個版本標識。當讀取資料時,將版本標識的值一同讀出,資料每更新一次同時對版本標識進行更新。當我們提交更新的時候,判斷資料庫表對應記錄的當前版本資訊與第一次取出來的版本標識進行比對,如果資料庫表當前版本號與第一次取出來的版本標識值相等,則予以更新,否則認為是過期資料。一般地,實現資料版本有兩種方式,一種是使用版本號,另一種是使用時間戳

  • 併發測試工具ab

    這是apache自帶的壓測工具,也可以拿來做併發測試

    使用方法 進入ab工具預設安裝目錄(注意這是windows環境下)

    V~J7722)6O9BS}I%)K~}1VG.png

執行命令 ./ab.exe -n 1000 -c 1000 http://localhost/tp5/public/

0_}27`@@E9Y1WMH8}62ERE8.png

下面是測試程式碼,用的是tp5

<?php
namespace app\index\controller;

use think\Controller;
use think\Db;

class Index extends Controller
{
    public function index()
    {
        Db::startTrans();
        try{
            $db = Db::table('test');
            $res = $db->where('id',1)->value('stock');//這樣庫存有可能會變負數
            //$res = $db->where('id',1)->lock(true)->value('stock');//這句解決併發帶來的問題,比如庫存負數
            if ($res >= 1){
                Db::table('test')->where('id', 1)->setDec('stock');
            }

            // 提交事務
            Db::commit();

        } catch (\Exception $e) {
            // 回滾事務
            Db::rollback();
            echo $e->getMessage();
        }
    }
}

複製程式碼

部分參考自: blog.csdn.net/justloveyou…

相關文章