1. 程式人生 > >事務的隔離級別

事務的隔離級別

mic name 釋放 pda 情況 pub cep har 能夠

  1. 事務的概念及特性

事務,一般是指要做的或所做的事情。在計算機術語中是指訪問並可能更新數據庫中各種數據項的一個程序執行單元(unit).

例如:在關系數據庫中,一個事務可以是一條SQL語句,一組SQL語句或整個程序。

特性

事務是恢復和並發控制的基本單位。

事務應該具有4個屬性:原子性、一致性、隔離性、持久性。這四個屬性通常稱為ACID特性

原子性(atomicity)。一個事務是一個不可分割的工作單位,事務中包括的諸操作要麽都做,要麽都不做。

一致性(consistency)。事務必須是使數據庫從一個一致性狀態變到另一個一致性狀態。一致性與原子性是密切相關的。

隔離性(isolation)。一個事務的執行不能被其他事務幹擾。即一個事務內部的操作及使用的數據對並發的其他事務是隔離的,並發執行的各個事務之間不能互相幹擾。

持久性(durability)。持久性也稱永久性(permanence),指一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。

  1. 事務的隔離級別

定義:

在數據庫操作中,為了有效保證並發讀取數據的正確性,提出的事務隔離級別

問題的提出:

數據庫是要被廣大客戶所共享訪問的,那麽在數據庫操作過程中很可能出現以下幾種不確定的情況.

更新丟失

兩個事務都同時更新一行數據,一個事務對數據的更新把另一個事務對數據的更新覆蓋了。這是因為系統沒有執行任何的鎖操作,因此並發事務並沒有被隔離開來。

臟讀

一個事務讀取到了另一個事務未提交的數據操作結果。這是相當危險的,因為很可能所有的操作都被回滾。

不可重復讀

不可重復讀(Non-repeatable Reads):一個事務對同一行數據重復讀取兩次,但是卻得到了不同的結果。

包括以下情況:

(1) 虛讀:事務T1讀取某一數據後,事務T2對其做了修改,當事務T1再次讀該數據時得到與前一次不同的值。

(2) 幻讀(Phantom Reads):事務在操作過程中進行兩次查詢,第二次查詢的結果包含了第一次查詢中未出現的數據或者缺少了第一次查詢中出現的數據(這裏並不要求兩次查詢的SQL語句相同)。這是因為在兩次查詢過程中有另外一個事務插入數據造成的。

解決方案

為了避免上面出現的幾種情況,在標準SQL規範中,定義了4個事務隔離級別,不同的隔離級別對事務的處理不同。

未授權讀取

也稱為讀未提交(Read Uncommitted):允許臟讀取,但不允許更新丟失。如果一個事務已經開始寫數據,則另外一個事務則不允許同時進行寫操作,但允許其他事務讀此行數據。該隔離級別可以通過“排他寫鎖”實現。

授權讀取

也稱為讀提交(Read Committed):允許不可重復讀取,但不允許臟讀取。這可以通過“瞬間共享讀鎖”和“排他寫鎖”實現。讀取數據的事務允許其他事務繼續訪問該行數據,但是未提交的寫事務將會禁止其他事務訪問該行。

可重復讀取(Repeatable Read)

可重復讀取(Repeatable Read):禁止不可重復讀取和臟讀取,但是有時可能出現幻讀數據。這可以通過“共享讀鎖”和“排他寫鎖”實現。讀取數據的事務將會禁止寫事務(但允許讀事務),寫事務則禁止任何其他事務。

序列化(Serializable)

序列化(Serializable):提供嚴格的事務隔離。它要求事務序列化執行,事務只能一個接著一個地執行,不能並發執行。僅僅通過“行級鎖”是無法實現事務序列化的,必須通過其他機制保證新插入的數據不會被剛執行查詢操作的事務訪問到。

隔離級別越高,越能保證數據的完整性和一致性,但是對並發性能的影響也越大。對於多數應用程序,可以優先考慮把數據庫系統的隔離級別設為Read Committed。它能夠避免臟讀取,而且具有較好的並發性能。盡管它會導致不可重復讀、幻讀和第二類丟失更新這些並發問題,在可能出現這類問題的個別場合,可以由應用程序采用悲觀鎖或樂觀鎖來控制。

Mysql中查看事務隔離級別

SELECT @@tx_isolation

設置事務的隔離級別

SET tx_isolation=‘read-uncommitted‘;

SET tx_isolation=‘read-committed‘

SET tx_isolation=‘repeatable-read‘;

package cn.bdqn.test;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.SQLException;

import java.sql.Statement;

public class TransactionLevel {

public static void main(String[] args) {

// 1. 創建連接

Connection connection = createConnection();

try {

//設置手動關閉事務

connection.setAutoCommit(false);

transactionLevel(connection);

// 設置級別

connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);

transactionLevel(connection);

insert(connection);

delete(connection);

connection.commit();

} catch (SQLException e) {

e.printStackTrace();

try {

connection.rollback();

} catch (SQLException e1) {

e1.printStackTrace();

}

}

close(connection);

}

//獲取數據庫鏈接

public static Connection createConnection(){

Connection connection = null;

try {

Class.forName("com.mysql.jdbc.Driver");

connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test","root","root");

} catch (Exception e) {

e.printStackTrace();

}

return connection;

}

//新增用戶

public static void insert(Connection connection)throws SQLException{

Statement stat = connection.createStatement();

String sql = "insert into emp(name,salary) value(‘haha‘,5000)";

stat.execute(sql);

close(stat);

}

//刪除指定的用戶

public static void delete(Connection connection)throws SQLException{

Statement stat = connection.createStatement();

String sql = "delete from emp where id = 5";

stat.execute(sql);

close(stat);

}

//關閉鏈接

public static void close(Connection connection){

if(connection!=null){

try {

connection.close();

} catch (SQLException e) {

e.printStackTrace();

}

connection=null;

}

}

//關閉鏈接

public static void close(Statement stat){

if(stat!=null){

try {

stat.close();

} catch (SQLException e) {

e.printStackTrace();

}

stat=null;

}

}

//獲取當前事務的級別

public static void transactionLevel(Connection connection) throws SQLException{

int level = connection.getTransactionIsolation();

if (level == Connection.TRANSACTION_NONE) {

System.out.println("當前事務的級別: TRANSACTION_NONE");

System.out.println(level);

} else if (level == Connection.TRANSACTION_READ_UNCOMMITTED) {

System.out.println("當前事務的級別: TRANSACTION_READ_UNCOMMITTED");

System.out.println(level);

} else if (level == Connection.TRANSACTION_READ_COMMITTED) {

System.out.println("當前事務的級別: TRANSACTION_READ_COMMITTED");

System.out.println(level);

} else if (level == Connection.TRANSACTION_REPEATABLE_READ) {

System.out.println("當前事務的級別: TRANSACTION_REPEATABLE_READ");

System.out.println(level);

} else if (level == Connection.TRANSACTION_SERIALIZABLE) {

System.out.println("當前事務的級別: TRANSACTION_SERIALIZABLE");

System.out.println(level);

}

}

}

Set transaction isolation level [read committed||serializable]

3. oracle中的鎖

排他鎖(Exclusive Locks,簡稱X鎖),又稱為寫鎖、獨占鎖,是一種基本的鎖類型。

定義

若事務T對數據對象A加上X鎖,則只允許T讀取和修改A,其他任何事務都不能再對A加任何類型的鎖,直到T釋放A上的鎖。這就保證了其他事務在T釋放A上的鎖之前不能再讀取和修改A。

Oracle數據庫支持多個用戶同時與數據庫進行交互,每個用戶都可以同時運行自己的事務,從而也需要對並發訪問進行控制。Oracle也是用“鎖”的機制來防止各個事務之間的相互影響,對並發訪問進行控制的,保證數據的一致性和完整性。當一個事務或操作企圖防止另一個事務對其操作的對象產生影響時,該事務或操作就對該對象進行鎖定,其他事務就只能在該事務釋放鎖之後才能操作該對象。

在大多數情況下,鎖對於開發人員來說是透明的,不用顯式地加鎖,即不用指定鎖的分類、級別、類型或模式。如,當更改記錄時,Oracle會自動地對相關的記錄加相應的鎖;當執行一個PL/SQL過程時,該過程就會自動地處於被鎖定的狀態,允許其他用戶執行它,但不允許其他用戶采用任何方式更改該過程。當然,Oracle也允許用戶使用Lock Table語句顯式地對被鎖定的對象加指定模式的鎖。

鎖有2種最基本、最簡單的類型:排他鎖(eXclusive lock,即X鎖)、共享鎖(Share lock,即S鎖)。這兩種鎖及其鎖定的示意圖如下圖所示。該圖中顯式了不同的鎖的作用,以及相互之間的相容規則。

排他鎖又稱為寫鎖。如果事務T在數據庫對象A上加了X鎖,則只允許T讀取、更改A。其他任何事務Ti都既不能對A加S鎖也不能加X鎖,直到T釋放A上的X鎖為止。這就保證了其他事務Ti在事務T釋放A上的X鎖之前,不能再讀取、更改、使用A,即保護A不被同時地讀、寫。

共享鎖又稱為讀鎖。如果事務T在數據庫對象A上加了S鎖,則只允許T讀取A但不能更改A。其他任何事務Ti都只能對A加S鎖而不能加X鎖,直到T釋放A上的S鎖為止。這就保證了其他事務Ti在事務T釋放A上的S鎖之前,只能讀取A但不能更改A,即保護A不被同時地寫。

鎖除了對操作進行限制外,鎖對鎖也進行限制,排他鎖與共享鎖的相容規則如表所示。

一、鎖定的粒度與意向鎖

鎖定對象的大小被稱為鎖定的粒度(granularity)。鎖定對象可以是邏輯單元(如數據庫、表、記錄、列、索引等),也可以是物理單元(如數據頁、索引頁等)。

鎖定的粒度與系統的並發度和並發控制的開銷密切相關。一般地,鎖定的粒度越大,需要鎖定的對象就越少,可選擇性就越小,並發度就越小,開銷就越小;反之,鎖定的粒度越小,需要鎖定的對象就越多,可選擇性就越大,並發度就越大,開銷就越大。

例如,如果鎖定的粒度是表,事務T1需要操作表A中的記錄r1,則T1必須對包含記錄r1的表A加鎖。在T1對A加鎖之後,事務T2需要操作表A中的記錄r2,因此就需要對表A加鎖,但此時因為T1已經在表A上加了鎖,所以T2的加鎖就會失敗,就被迫等待,直到T1釋放鎖為止,事務T1和T2就不能並發執行了;如果鎖定的粒度是記錄,則事務T1可以對表A中的記錄r1加鎖,同時事務T2也可以對表A中的記錄r2加鎖,所以事務T1和T2就可以並發執行了,並發度就增大了,但維護這個並發度就需要更多的開銷了。

1、多粒度鎖定與多粒度樹

如果在一個數據庫管理系統中,同時支持多種鎖定粒度供事務選擇,這種鎖定方法就被稱為多粒度鎖定(multiple granularity locking)。

如下圖所示是一個四級的多粒度樹。該樹的根節點是數據庫,表示最大的粒度;頁節點是列,表示最小的粒度。

從前面的例子可知,選擇鎖定粒度時應該同時考慮並發度與開銷兩個因素。以求最優的效果。一般地,需要處理大量記錄的事務可以以表為鎖定粒度;需要處理多個表中的大量記錄的事務可以以數據庫為鎖定粒度;而只需要處理某個表中少量記錄的事務,則可以以記錄為鎖定粒度。

多粒度鎖定協議是指,允許對多粒度樹中的節點單獨地加鎖,另外,對一個節點加鎖還意味著對這個節點的各級子節點也加同樣的鎖。

因此,可以用兩種方法對多粒度樹中的節點加鎖:顯式鎖定、隱式鎖定。顯式鎖定是在事務中明確指定要加在節點上的鎖;隱式鎖定是由於在其上級節點中加顯式鎖時而使該節點獲得的鎖。

在多粒度鎖定中,顯式鎖定與隱式鎖定的作用與相容規則都是一樣的。因此,當系統檢查鎖定的沖突時,不僅要檢查顯式鎖定還要檢查隱式鎖定。

一般地,當對一個節點加鎖時,系統第一要檢查在該節點上有無顯式鎖定與之沖突;第二要檢查其所有上級節點有無顯式鎖定與之沖突,以便查看在該節點上加該顯式鎖定,是否會與從上級節點獲得的隱式鎖定有沖突;第三要檢查所有下級節點有無顯式鎖定與之沖突,以便查看在該節點上加該顯式鎖定,是否會使下級節點從該節點獲得的隱式鎖定與其顯式鎖定有沖突。

這種檢查鎖定沖突的方法的效率很低,所以引進了意向鎖(Intended lock)的概念。

2、意向鎖

意向鎖的含義是,如果對一個節點加某種意向鎖,則會對該節點的各級下級節點加這種鎖;如果對一個節點加某種鎖,則必須先對該節點的各級上級節點加這種意向鎖。

例如,對記錄r1加鎖時,必須先對它所在的表A加意向鎖。於是,事務T1對表A加X鎖時,系統只需要檢查根節點數據庫D和表A是否已經加了不相容的鎖,而不再需要檢查表A中每個記錄是否加了X鎖。

有如下幾種意向鎖。

一、IS鎖(Intended Share lock,意向共享鎖)

如果對一個節點加IS鎖,則表示對它的所有下級節點有加S鎖的意向;如果對一個節點加S鎖,則必須先對該節點的各級上級節點加IS鎖。

二、IX鎖(Intended eXclusive lock,意向排他鎖)

如果對一個節點加IX鎖,則表示對它的所有下級節點有加X鎖的意向;如果對一個節點加X鎖,則必須先對該節點的各級上級節點加IX鎖。

三、SIX鎖(Share Intended eXclusive lock,共享意向排他鎖)

如果對一個節點加SIX鎖,則表示對它加S鎖,然後再加IX鎖,即SIX=S+IX。例如,如果事務T對表A加SIX鎖,則表示事務T要讀取整個表(S鎖的作用),同時還會更新某些記錄(IX鎖的作用)。

包括意向鎖的各種鎖之間的相容規則如表所示。

例如,當事務T對表A保持的鎖是S鎖時,事務Ti不可以獲得對表A的IX鎖,但可以獲得對A的IS鎖。所以S鎖與IX鎖的強度相當,但比IS鎖的強度要強。

同上理,可以逐個地推導出這些鎖的強度關系,如圖所示。圖中上面的鎖比下面的鎖的強度要強,同級的鎖強度相當。一個事務在申請鎖定時以強鎖代替弱鎖是安全的(但會降低並發度),反之則不然。申請鎖定時應該按自上而下的次序進行,釋放鎖定時則應該按自下而上的次序進行。

具有意向鎖的多粒度鎖定方法能提高系統的並發度,減少加鎖和解鎖的開銷,已經在實際DBMS中得到應用,如Oracle數據庫。

二、鎖的分類

Oracle主要有2種鎖:DDL鎖(字典鎖)、DML鎖(數據鎖)。

1 DDL

當用戶發布DDL(Data Definition Language)語句時會對涉及的對象加DDL鎖。由於DDL語句會更改數據字典,所以該鎖也被稱為字典鎖。

DDL鎖能防止在用DML語句操作數據庫表時,對表進行刪除,或對表的結構進行更改。

對於DDL鎖,要註意的是:

$$ DDL鎖只鎖定DDL操作所涉及的對象,而不會鎖定數據字典中的所有對象。

$$ DDL鎖由Oracle自動加鎖和釋放。不能顯式地給對象加DDL鎖,即沒有加DDL鎖的語句。

$$ 在過程中引用的對象,在過程編譯結束之前不能被改變或刪除,即不能被加排他DDL鎖。

DDL鎖的類型與特征如表所示。

DDL鎖很少會在系統裏引起爭用,因為它們的保持時間都非常短暫。但DDL鎖的存在卻不容忽視,尤其是在申請排他DDL鎖時。

2DML

當用戶發布DML(Data Manipulation Language)語句(如insert、update、delete)時會對涉及的對象加DML鎖。由於DML語句涉及的是數據操作,所以該鎖也被稱為數據鎖。

DML鎖能防止多個事務並發訪問數據時,對數據的一致性和完整性的破壞。

根據鎖定的粒度和意向,DML鎖有幾種模式。在執行Lock Table語句或執行不同的DML語句時會加不同模式的DML鎖。當一個事務在一個表上加了某種模式的DML鎖之後,另一個事務在該表上所能執行的DML操作會受到限制,如下表所示。其中有的鎖模式有2個名稱,這是因為不同的Oracle版本在提到它們時使用了不同的表示方法。

三、死鎖現象及其解決

死鎖是一種比較嚴重的、特殊的鎖爭用類型。在這種鎖爭用中,兩個或兩個以上的用戶正在等待對方鎖定的資源。因此,如果不進行某種幹預,任意一個事務都無法完成。

假設當前有兩個會話,分別執行的事務如圖所示。顯然這兩個會話之間由於互相鎖定了對方所需要鎖定的對象,所以發生了死鎖。

“提示”Oracle提供了有效的死鎖檢測機制,周期性地(通常只有幾秒鐘)診斷系統中有無死鎖,並提示用戶。

當Oracle檢測到死鎖後,會在預警文件和跟蹤文件中記錄下死鎖的信息,在跟蹤文件中詳細地記錄了檢測到死鎖的時間、被死鎖的語句、阻塞者會話、等待者會話、操作系統用戶的信息(如用戶名、用戶所在的計算機、用戶使用的應用程序)等有助於確定死鎖及其處理方法的信息。

即使Oracle為會話A提示了檢測到死鎖的信息,並且回退了會話A的第2個update語句,但會話A仍然在第1個update語句上保持著鎖,並阻塞了會話B的第2個update語句.

當出現檢測到死鎖的信息後,用戶自己(或DBA通知相應的用戶)執行commit語句或rollback語句,使事務結束,釋放所加的鎖。還可以使用 ALTER SYSTEM KILL SESSION ‘sid, serial#‘ 語句來殺死會話,強行解決鎖爭用。

事務的隔離級別