從事一個項目,需要考慮數據的安全性,之前對於數據庫這部分的數據操作學習的比較零散,由於手頭的項目,於是系統的
學習了下數據庫操作加鎖的知識:
--------------------------------------------------華麗麗的分割線-----------------------------------------------------------
學習一個知識,我們大致都會經歷這么幾個過程(what this ? why to use ? how to use?),首先,我們需要搞懂,下面幾個知識點:
一: 什么是數據庫加鎖 ?
數據庫加鎖: 簡單的意思就是對於在執行一個操作(比如修改)時,對這個操作的對象加鎖,放置其他操作讀取到臟數據或者幽靈數據。
或者術語來說就是一種排他鎖,當寫的時候不允許其他程序寫,這樣就可以保證數據一致性了
二:為什么要給數據加鎖?
對於這點,我們需要簡單的了解幾個概念:
(1).什么是事務?
事務: 是用戶定義的數據庫操作系列,這些操作作為一個完整的工作單元執行。一個事務內的所有語句作為一個整體。要么全部執行,要么全部不執行。
事務的幾個特點: 原子性,一致性,隔離性,持久性, 簡稱ACID特征
一般來講: 事務的這幾個特點被遭到破壞的有下列幾個情況:
(1) 多事務並行運行時,不同事務的操作有交叉情況。(->_-> 如果你想不到列子:就想想多線程問題)
(2) 事務在運行過程中被強迫停止。
(2)什么是臟讀:
臟讀 :臟讀就是指當一個事務正在訪問數據,並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,另外一個事務也訪問這個數據,然后使用了這個數據。
(3)什么是不可重復讀?
不可重復讀 :是指在一個事務內,多次讀同一數據。在這個事務還沒有結束時,另外一個事務也訪問該同一數據。那么,在第一個事務中的兩次讀數據之間,由於第二個事務的修改,那么第一個事務兩次讀到的的數據可能是不一樣的。這樣就發生了在一個事務內兩次讀到的數據是不一樣的,因此稱為是不可重復讀。例如,一個編輯人員兩次讀取同一文檔,但在兩次讀取之間,作者重寫了該文檔。當編輯人員第二次讀取文檔時,文檔已更改。原始讀取不可重復。如果只有在作者全部完成編寫后編輯人員才可以讀取文檔,則可以避免該問題。
(4)什么是幻讀?
幻讀 : 是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的全部數據行。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那么,以后就會發生操作第一個事務的用戶發現表中還有沒有修改的數據行,就好象發生了幻覺一樣。例如,一個編輯人員更改作者提交的文檔,但當生產部門將其更改內容合並到該文檔的主復本時,發現作者已將未編輯的新材料添加到該文檔中。如果在編輯人員和生產部門完成對原始文檔的處理之前,任何人都不能將新材料添加到文檔中,則可以避免該問題。
因為在上述的情況下,數據會出現臟數據。對於一個考慮安全性的系統而言,加鎖自然是十分必要.
(三)如何對數據加鎖:
對於數據加鎖: 一般分為如下兩種,第一類,就是數據庫自己加鎖,第二類,就是線程鎖。
第一種: 數據庫自己加鎖
對於鎖的級別: 庫級鎖,表級鎖,頁級鎖,行級鎖。(這篇文章提供了較多的說明)
http://blog.csdn.net/cspyb/article/details/1865538
舉幾個列子:對於數據表的加鎖:
方法一: 使用SQL語句進行加鎖
public void test() { String sql = "lock tables Gxjun write"; // 或String sql = "lock tables Gxjun read"; // 如果想鎖多個表 lock tables Gxjun read stu write , ..... String sql1 = "select * from Gxjun "; String sql2 = "unlock tables"; try { this.pstmt = conn.prepareStatement(sql); this.pstmt1 = conn.prepareStatement(sql1); this.pstmt2 = conn.prepareStatement(sql2); pstmt.executeQuery(); pstmt1.executeQuery(); pstmt2.executeQuery(); } catch (Exception e) { System.out.println("異常" + e.getMessage()); } }
方法二 , 采用記錄鎖加鎖:
public void test() { String sql = "select * from Gxjun for update"; try { conn.setAutoCommit(false); this.pstmt = conn.prepareStatement(sql); pstmt.executeQuery(); } catch (Exception e) { System.out.println("異常" + e.getMessage()); } }
需要標注的幾點就是:(摘錄自思緒飛帆,鳴謝----思緒飛帆)
/* 1.for update 與 lock in share mode 屬於行級鎖和頁級鎖 2.for update 排它鎖,lock in share mode 共享鎖 3.對於記錄鎖.必須開啟事務. 4.行級鎖定事實上是索引記錄的鎖定.只要是用索引掃描的行(或沒索引全表掃描的行),都將被鎖住. 5.在不同的隔離級別下還會使用next-key locking算法.即所掃描的行之間的“間隙”也會也鎖住(在Repeatable read和Serializable隔離級別下有間隙鎖). 6.在mysql中共享鎖的含義是:在被共享鎖鎖住的行,即使內容被修改且並沒有提交.在另一個會話中依然看到最新修改的信息. 在同一會話中加上了共享鎖.可以對這個表以及這個表以外的所有表進行增、刪、改、查的操作. 在不同的會話中.可以查到共享鎖鎖住行的最新消息.但是在Read Uncommitted隔離級別下不能對鎖住的表進行刪, 改操作.(需要等待鎖釋放才能操作...) 在Read Committed隔離級別下不能對鎖住的表進行刪,改操作.(需要等待鎖釋放才能操作...) 在Repeatable read隔離級別下不能對鎖住行進行增、刪、改操作.(需要等待鎖釋放才能操作...) 在Serializable隔離級別下不能對鎖住行進行增、刪、改操作. (需要等待鎖釋放才能操作...) 7.在mysql中排他鎖的含義是:在被排它鎖鎖住的行,內容修改並沒提交,在另一個會話中不會看到最新修改的信息。 在不同的會話中.可以查到共享鎖鎖住行的最新消息.但是Read Uncommitted隔離級別下不能對鎖住的表進行刪, 改操作.(需要等待鎖釋放才能操作...) 在Read Committed隔離級別下不能對鎖住的表進行刪,改操作.(需要等待鎖釋放才能操作...) 在Repeatable read隔離級別下不能對鎖住行進行增、刪、改操作.(需要等待鎖釋放才能操作...) 在Serializable隔離級別下不能對鎖住行進行增、刪、改操作. (需要等待鎖釋放才能操作...) 8.在同一個會話中的可以疊加多個共享鎖和排他鎖.在多個會話中,需要等待鎖的釋放. 9.SQL中的update 與 for update是一樣的原理. 10.等待超時的參數設置:innodb_lock_wait_timeout=50 (單位秒). 11.任何可以觸發事務提交的命令,都可以關閉共享鎖和排它鎖. */
第二種: 就是線程鎖,這是我重點學習的地方,(額,需呀注解的是,是我重點學習的地方,然后每個人注重點不同)
引用到的材料:
1. http://lavasoft.blog.51cto.com/62575/99155
2.http://www.cnblogs.com/hoojo/archive/2011/05/05/2038101.html
3. http://www.blogjava.net/zhangwei217245/archive/2010/04/08/315526.html
-------------------------------------------------------------------------------------------------------------------------------------- 鳴謝上述作者
0-----------------------------------------------------------------華麗麗的分割線----------------------------------------------------------------------0
舉列子:
在不采用同步鎖的情況下

1 package Day_2; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 /** 6 * @author Gxjun 7 * 功能:同步鎖 8 */ 9 public class Demo_tongbusuo { 10 public static void main(String args []){ 11 MyThread r = new MyThread(); 12 //ExecutorService MyThreadPool = Executors.newCachedThreadPool(); 13 Thread ta = new Thread(r, "王小二"); 14 Thread tb = new Thread(r, "王小三"); 15 //加載線程池中去 16 // MyThreadPool.execute(ta); 17 //MyThreadPool.execute(tb); 18 ta.start(); 19 tb.start(); 20 } 21 } 22 23 class Foo { 24 private int x = 100; 25 26 public int getX() { 27 return x; 28 } 29 30 public int fix(int y) { 31 x = x - y; 32 return x; 33 } 34 } 35 36 class MyThread implements Runnable { 37 private Foo foo = new Foo(); 38 39 public void run() { 40 for (int i = 0; i < 3; i++) { 41 this.fix(30); 42 try { 43 Thread.sleep(1); 44 } catch (InterruptedException e) { 45 e.printStackTrace(); 46 } 47 System.out.println(Thread.currentThread().getName() + " : 當前foo對象的x值= " + foo.getX()); 48 } 49 } 50 51 public int fix(int y) { 52 return foo.fix(y); 53 } 54 }
結果為:
王小二 : 當前foo對象的x值= 40
王小三 : 當前foo對象的x值= 40
王小二 : 當前foo對象的x值= -20
王小三 : 當前foo對象的x值= -50
王小二 : 當前foo對象的x值= -80
王小三 : 當前foo對象的x值= -80
但是對於上述問題,采用線程池,卻能得到完滿的解決。
將其改成同步鎖之后:

1 package Day_2; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 /** 6 * @author Gxjun 7 * 功能:同步鎖 8 */ 9 public class Demo_tongbusuo { 10 public static void main(String args []){ 11 MyThread r = new MyThread(); 12 //ExecutorService MyThreadPool = Executors.newCachedThreadPool(); 13 Thread ta = new Thread(r, "王小二"); 14 Thread tb = new Thread(r, "王小三"); 15 //加載線程池中去 16 // MyThreadPool.execute(ta); 17 //MyThreadPool.execute(tb); 18 ta.start(); 19 tb.start(); 20 } 21 } 22 23 class Foo { 24 private int x = 100; 25 26 public int getX() { 27 //改為同步鎖 28 synchronized (this) { 29 return x; 30 } 31 } 32 33 public int fix(int y) { 34 x = x - y; 35 return x; 36 } 37 } 38 39 class MyThread implements Runnable { 40 private Foo foo = new Foo(); 41 42 public void run() { 43 for (int i = 0; i < 3; i++) { 44 this.fix(30); 45 try { 46 Thread.sleep(1); 47 } catch (InterruptedException e) { 48 e.printStackTrace(); 49 } 50 System.out.println(Thread.currentThread().getName() + " : 當前foo對象的x值= " + foo.getX()); 51 } 52 } 53 54 public int fix(int y) { 55 return foo.fix(y); 56 } 57 }
結果:
王小二 : 當前foo對象的x值= 40
王小三 : 當前foo對象的x值= 40
王小三 : 當前foo對象的x值= -20
王小二 : 當前foo對象的x值= -20
王小三 : 當前foo對象的x值= -80
王小二 : 當前foo對象的x值= -80
這個結果和采用線程池得到結果是一樣的........
采用非同步鎖結果也是一樣的.....

1 package Day_2; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 /** 6 * @author Gxjun 7 * 功能:同步鎖 8 */ 9 public class Demo_tongbusuo { 10 public static void main(String args []){ 11 MyThread r = new MyThread(); 12 //ExecutorService MyThreadPool = Executors.newCachedThreadPool(); 13 Thread ta = new Thread(r, "王小二"); 14 Thread tb = new Thread(r, "王小三"); 15 //加載線程池中去 16 // MyThreadPool.execute(ta); 17 //MyThreadPool.execute(tb); 18 ta.start(); 19 tb.start(); 20 } 21 } 22 23 class Foo { 24 private int x = 100; 25 26 public synchronized int getX() { 27 return x; 28 } 29 30 public int fix(int y) { 31 x = x - y; 32 return x; 33 } 34 } 35 36 class MyThread implements Runnable { 37 private Foo foo = new Foo(); 38 39 public void run() { 40 for (int i = 0; i < 3; i++) { 41 this.fix(30); 42 try { 43 Thread.sleep(1); 44 } catch (InterruptedException e) { 45 e.printStackTrace(); 46 } 47 System.out.println(Thread.currentThread().getName() + " : 當前foo對象的x值= " + foo.getX()); 48 } 49 } 50 51 public int fix(int y) { 52 return foo.fix(y); 53 } 54 }