天天看點

【讀書筆記】企業應用架構模式——并發、事務與鎖

一、并發問題的産生 多線程/程序同時操作(讀/寫)同一資料

二、并發問題的種類

  • 丢失更新(lost update)
    • 第一類更新丢失(復原丢失):   當2個事務更新相同的資料源,如果第一個事務被送出,而另外一個事務卻被撤銷,那麼會連同第一個事務所做的跟新也被撤銷。也就是說第一個事務做的跟新丢失了。
    • 第二類更新丢失(覆寫丢失):   第二類更新丢失實在實際應用中經常遇到的并發問題,他和不可重複讀本質上是同一類并發問題,通常他被看做不可重複讀的特例。當2個或這個多個事務查詢同樣的記錄然後各自基于最初的查詢結果更新該行時,會造成第二類丢失更新。因為每個事務都不知道不知道其他事務的存在,最後一個事務對記錄做的修改将覆寫其他事務對該記錄做的已送出的跟新。
  • 髒讀(dirty read)
    •  髒讀(事務沒送出,提前讀取):髒讀就是指當一個事務正在通路資料,并且對資料進行了修改,而這種修改還沒有送出到資料庫中,這時,另外一個事務也通路這個資料,然後使用了這個資料。 
  • 不可重複讀(non-repeatable read)
    • 不可重複讀(兩次讀的不一緻) :是指在一個事務内,多次讀同一資料。在這個事務還沒有結束時,另外一個事務也通路該同一資料。那麼,在第一個事務中的兩次讀資料之間,由于第二個事務的修改,那麼第一個事務兩次讀到的的資料可能是不一樣的。這樣就發生了在一個事務内兩次讀到的資料是不一樣的,是以稱為是不可重複讀。例如,一個編輯人員兩次讀取同一文檔,但在兩次讀取之間,作者重寫了該文檔。當編輯人員第二次讀取文檔時,文檔已更改。原始讀取不可重複。如果隻有在作者全部完成編寫後編輯人員才可以讀取文檔,則可以避免該問題。 
  • 幻讀(phantom read)
    • 幻讀(發生在其他事務插入或删除後):是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的資料進行了修改,這種修改涉及到表中的全部資料行。同時,第二個事務也修改這個表中的資料,這種修改是向表中插入一行新資料。那麼,以後就會發生操作第一個事務的使用者發現表中還有沒有修改的資料行,就好象發生了幻覺一樣。例如,一個編輯人員更改作者送出的文檔,但當生産部門将其更改内容合并到該文檔的主複本時,發現作者已将未編輯的新材料添加到該文檔中。如果在編輯人員和生産部門完成對原始文檔的處理之前,任何人都不能将新材料添加到文檔中,則可以避免該問題。 

http://msdn.microsoft.com/en-us/library/aa213029(v=sql.80).aspx http://www.cnblogs.com/pyq228/archive/2012/05/26/2519447.html

三、解決方案

  • 隔離——使資料局部化(即,使用非共享資料、線程獨享),則線程安全
  • 識别不變的資料——盡量使用常量
  • 加鎖——對不得不共享的可變資料進行加鎖

四、樂觀鎖政策——沖突檢測 樂觀鎖政策通常是建立在資料的某種版本标記上。為了檢測“丢失更新”,系統核對将要更新的資料的版本标記和共享資料的版本标記,如果兩者一樣,系統更新資料并更新版本标記,否則,隻能放棄本次更新,從頭再來。

五、悲觀鎖政策——沖突避免 讀鎖(共享鎖S):對讀鎖開放,對寫鎖封閉( lock table 表名 in share mode) 寫鎖(排他說X):對讀鎖和寫鎖都封閉( select * from 表名 where 條件 for update)

六、死鎖——悲觀鎖會導緻死鎖 死鎖解除方案:死鎖逾時控制(會導緻誤傷持鎖時間長的)、死鎖檢測機制 死鎖預防方案:

  • 強制在開始時就獲得所有可能需要的鎖,此後就不允許再獲得更多的鎖。
  • 規定每個人擷取鎖的順序。例如按字母順序。
  • 當取不到鎖的時候,自動犧牲。
  • 。。。。

七、事務——處理并發問題的主要工具 ACID屬性:原子性(Actomicity)、一緻性(Consistency)、隔離性(Isolation)、持久性(Durability) 使用事務時,要防止 鎖更新(lock escalation),如果一個事務鎖住了一個表中的許多行,則資料庫可能無法處理那麼多鎖,隻能将鎖更新到鎖住整個表。

八、事務隔離級别——事務之間用鎖互相隔開 隔離級别就是對事務并發控制的等級。ANSI/ ISO SQL将其分為串行化(SERIALIZABLE)、可重複讀(REPEATABLE READ)、讀已送出(READ COMMITED)、讀未送出(READ UNCOMMITED)四個等級。為了實作隔離級别通常資料庫 采用鎖(Lock)。一般在程式設計的時候隻需要設定隔離等級,至于具體采用什麼鎖則由資料庫來設定。

【讀書筆記】企業應用架構模式——并發、事務與鎖

九、JDBC定義的事務隔離級别

  • Connection. TRANSACTION_NONE 說明不支援事務。
  • Connection. TRANSACTION_READ_UNCOMMITTED 說明在送出前一個事務可以看到另一個事務的變化。這樣髒讀、不可重複的讀和虛讀都是允許的。
  • Connection. TRANSACTION_READ_COMMITTED 說明讀取未送出的資料是不允許的。這個級别仍然允許不可重複的讀和虛讀産生。
  • Connection. TRANSACTION_REPEATABLE_READ 說明事務保證能夠再次讀取相同的資料而不會失敗,但虛讀仍然會出現。
  • Connection. TRANSACTION_SERIALIZABLE 是最高的事務級别,它防止髒讀、不可重複的讀和虛讀。

十、Spring事務隔離級别的定義和配置

  • TransactionDefinition.ISOLATION_DEFAULT 這是一個PlatfromTransactionManager預設的隔離級别,使用資料庫預設的事務隔離級别.另外四個與JDBC的隔離級别相對應 
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED 這是事務最低的隔離級别,它充許别外一個事務可以看到這個事務未送出的資料。這種隔離級别會産生髒讀,不可重複讀和幻像讀.
  • TransactionDefinition.ISOLATION_READ_COMMITTED 保證一個事務修改的資料送出後才能被另外一個事務讀取。另外一個事務不能讀取該事務未送出的資料。這種事務隔離級别可以避免髒讀出現,但是可能會出現不可重複讀和幻像讀。
  • TransactionDefinition.ISOLATION_REPEATABLE_READ 這種事務隔離級别可以防止髒讀,不可重複讀。但是可能出現幻像讀。它除了保證一個事務不能讀取另一個事務未送出的資料外,還保證了避免下面的情況産生(不可重複讀)。
  • TransactionDefinition.ISOLATION_SERIALIZABLE 這是花費最高代價但是最可靠的事務隔離級别。事務被處理為順序執行。除了防止髒讀,不可重複讀外,還避免了幻像讀。

<tx:advice id="txAdvice" transaction-manager="transactionManager">   <tx:attributes>           <tx:method name="*" propagation="REQUIRED"  isolation="READ_COMMITTED" />   </tx:attributes> </tx:advice>

十一、幾大資料庫的預設事務隔離級别 隔離級别每種資料庫都不一樣,如果不指定這個屬性的話,就是 default

  • SQL Server :Read Commited
  • Oracle:        Read Commited
  • MySQL :      Repeatable Read

附錄一:Oracle的鎖 Oracle鎖分為兩大類:資料鎖(DML鎖)和字典鎖。字典鎖包括文法分析鎖和DDL鎖,有DBMS控制,使用者無權控制。 Oracle 5種資料鎖:共享鎖、排他鎖、行級共享鎖(RS鎖)、行級排他鎖(RX鎖)、共享行級排他鎖(SRX鎖)。加鎖粒度包括行級和表級。 資料鎖相容矩陣:           S    X    RS    RX    SRX  S        Y    N    Y      N        N X        N    N    N      N       N RS      Y    N    Y      Y        Y RX      N    N    Y      Y        N SRX    N    N    Y      N        N

一般情況下,Oracle自行加鎖,使用者也可以通過lock table等語句進行加鎖。 Oracle預設情況下,讀資料不加鎖,而是通過復原段防止髒讀和保證可重複讀。 Oracle具有死鎖檢查功能,周期性檢查系統是否有死鎖,如果存在死鎖,則撤銷執行更新操作次數最少的事務。

附錄二:JDBC事務隔離級别的測試

@Test
	public void testDbTransactionIsolation() throws ClassNotFoundException,
			SQLException {
		Connection cn = getConnection();
		System.out.print("預設的事務隔離級别是:");
		printTransactionIsolation(cn);

		setTransactionIsolation(cn, Connection.TRANSACTION_NONE,
				"TRANSACTION_NONE");
		setTransactionIsolation(cn, Connection.TRANSACTION_READ_UNCOMMITTED,
				"TRANSACTION_READ_UNCOMMITTED");
		setTransactionIsolation(cn, Connection.TRANSACTION_READ_COMMITTED,
				"TRANSACTION_READ_COMMITTED");
		setTransactionIsolation(cn, Connection.TRANSACTION_REPEATABLE_READ,
				"TRANSACTION_REPEATABLE_READ");
		setTransactionIsolation(cn, Connection.TRANSACTION_SERIALIZABLE,
				"TRANSACTION_SERIALIZABLE");

		cn.close();
	}

	private void setTransactionIsolation(Connection cn, int level,
			String levelDiscription) {
		try {
			System.out.print("設定事務隔離級别為:" + levelDiscription + ",");
			cn.setTransactionIsolation(level);
			System.out.print("設定成功,");
			printTransactionIsolation(cn);
		} catch (Exception e) {
			System.out.println("設定失敗:" + e.getMessage());
		}
	}

	private void printTransactionIsolation(Connection cn) throws SQLException {
		int level = cn.getTransactionIsolation();
		if (level == Connection.TRANSACTION_NONE)
			System.out.println("TRANSACTION_NONE");
		else if (level == Connection.TRANSACTION_READ_UNCOMMITTED)
			System.out.println("TRANSACTION_READ_UNCOMMITTED");
		else if (level == Connection.TRANSACTION_READ_COMMITTED)
			System.out.println("TRANSACTION_READ_COMMITTED");
		else if (level == Connection.TRANSACTION_REPEATABLE_READ)
			System.out.println("TRANSACTION_REPEATABLE_READ");
		else if (level == Connection.TRANSACTION_SERIALIZABLE)
			System.out.println("TRANSACTION_SERIALIZABLE");
	}