2011-01-13 93 views
6

我有MySQL的SELECT問題.. FOR UPDATE,這裏是我試圖運行查詢:JDBC鎖使用SELECT FOR UPDATE行,不行

SELECT * FROM tableName WHERE HostName='UnknownHost' 
     ORDER BY UpdateTimestamp asc limit 1 FOR UPDATE 

在此之後,有關線程將執行UPDATE並更改HostName,然後它應該解鎖該行。

我運行一個多線程Java應用程序,所以3個線程運行該SQL語句,但是當線程1個運行此,它不會從線程鎖定其結果2 &。因此線程2 & 3越來越相同的結果,他們可以更新同一行。

另外每個線程都在它自己的mysql連接上。

我使用的是InnoDB,與事務隔離=讀取 - 提交,並關閉自動提交執行SELECT FOR UPDATE

我可能會錯過一些前?或者也許有更好的解決方案? 非常感謝。

代碼:

public BasicJDBCDemo() 
{ 
    Le_Thread newThread1=new Le_Thread(); 
    Le_Thread newThread2=new Le_Thread(); 
    newThread1.start(); 
    newThread2.start();   
} 

主題:

class Le_Thread extends Thread 
{ 

    public void run() 
    { 
    tring name = Thread.currentThread().getName(); 
     System.out.println(name+": Debut."); 
    long oid=Util.doSelectLockTest(name); 
    Util.doUpdateTest(oid,name);   
    } 

} 

選擇:

public static long doSelectLockTest(String threadName) 
    { 
    System.out.println("[OUTPUT FROM SELECT Lock ]...threadName="+threadName); 
    PreparedStatement pst = null; 
    ResultSet rs=null; 
    Connection conn=null; 
    long oid=0; 
    try 
    { 
    String query = "SELECT * FROM table WHERE Host=? 
           ORDER BY Timestamp asc limit 1 FOR UPDATE"; 


     conn=getNewConnection(); 
     pst = conn.prepareStatement(query); 
     pst.setString(1, DbProperties.UnknownHost); 
     System.out.println("pst="+threadName+"__"+pst); 
     rs = pst.executeQuery(); 

     if (rs.first()) 
     { 
     String s = rs.getString("HostName"); 
     oid = rs.getLong("OID"); 
     System.out.println("oid_oldest/host/threadName=="+oid+"/"+s+"/"+threadName); 

     } 

    } 
    catch (SQLException ex) 
    { 
     ex.printStackTrace(); 
    } 
    finally 
    { 
     DBUtil.close(pst); 
     DBUtil.close(rs); 
     DBUtil.close(conn); 
    } 
    return oid; 
    } 

請幫助....:

結果:

 
Thread-1: Debut. 
Thread-2: Debut. 
[OUTPUT FROM SELECT Lock ]...threadName=Thread-1 
New connection.. 
[OUTPUT FROM SELECT Lock ]...threadName=Thread-2 
New connection.. 
pst=Thread-2: SELECT * FROM b2biCheckPoint WHERE HostName='UnknownHost' ORDER BY UpdateTimestamp asc limit 1 FOR UPDATE 
pst=Thread-1: SELECT * FROM b2biCheckPoint WHERE HostName='UnknownHost' ORDER BY UpdateTimestamp asc limit 1 FOR UPDATE 
oid_oldest/host/threadName==1/UnknownHost/Thread-2 
oid_oldest/host/threadName==1/UnknownHost/Thread-1 
[Performing UPDATE] ... oid = 1, thread=Thread-2 
New connection.. 
[Performing UPDATE] ... oid = 1, thread=Thread-1 
pst_threadname=Thread-2: UPDATE b2bicheckpoint SET HostName='1_host_Thread-2',UpdateTimestamp=1294940161838 where OID = 1 
New connection.. 
pst_threadname=Thread-1: UPDATE b2bicheckpoint SET HostName='1_host_Thread-1',UpdateTimestamp=1294940161853 where OID = 1 
+0

你是如何實現這個代碼明智的? – 2011-01-13 17:22:14

+0

你是什麼意思? – Rachid 2011-01-13 18:10:17

回答

1

您創建的選擇更新的連接需要與用於更新的連接相同。否則,它不是同一個事務的一部分,它釋放鎖,所以你的其他線程也開始執行它。因此,在你的代碼你需要這樣做:

if (rs.first()) 
    { 
    String s = rs.getString("HostName"); 
    oid = rs.getLong("OID"); 
    System.out.println("oid_oldest/host/threadName=="+oid+"/"+s+"/"+threadName); 

    } 
Util.doUpdateTest(oid,name,conn); 
conn.commit(); 
+0

Hi Hi2k,謝謝你的回答。我無法更改數據庫服務器配置,我的意思是我無法更改my.ini文件。但是在MySQL手冊中,他們說如果設置了READ-COMMITTED,只有索引會被鎖定,這對我來說是可以的,因爲HostName列是一個索引.... – Rachid 2011-01-13 17:27:41

0

好吧,這裏是我的新selectLockTest方法:

代碼:

public static long doSelectLockTest(String threadName) { 
    System.out.println("[OUTPUT FROM SELECT Lock ]...threadName=" + threadName); 
    PreparedStatement pst = null; 
    ResultSet rs = null; 
    Connection conn = null; 
    long oid = 0; 
    try { 
     String query = "SELECT * FROM table WHERE Host=? ORDER BY UpdateTime asc limit 1 FOR UPDATE"; 

     conn = getNewConnection(); 
     conn.setAutoCommit(false); 
     pst = conn.prepareStatement(query); 

     pst.setString(1, DbProperties.UnknownHost); 
     rs = pst.executeQuery(); 

     if (rs.first()) { 
      String s = rs.getString("HostName"); 
      oid = rs.getLong("OID"); 

      //Start update then commit 
      if (oid != 0) { 

       query = "UPDATE b2bicheckpoint SET HostName=?,UpdateTimestamp=? where OID = ?"; 

       pst = conn.prepareStatement(query); 
       pst.setString(1, oid + "_host_" + threadName); 
       pst.setLong(2, getCurrentLongTime()); 
       pst.setLong(3, oid); 
       System.out.println("Select_Prestatement=" + threadName + "__" + pst); 

       int result = pst.executeUpdate(); 
       conn.commit(); 
       conn.setAutoCommit(true); 
      } 

     } 

    } catch (SQLException ex) { 
     ex.printStackTrace(); 
    } finally { 
     DBUtil.close(pst); 
     DBUtil.close(rs); 
     DBUtil.close(conn); 
    } 
    return oid; 
} 

結果:

[OUTPUT FROM SELECT Lock ]...threadName=Thread-1 
[OUTPUT FROM SELECT Lock ]...threadName=Thread-2 
Select_Prestatement=Thread-1_ : SELECT * FROM ..... FOR UPDATE 
Select_Prestatement=Thread-2_: SELECT * FROM ...... FOR UPDATE 
Select_Prestatement=Thread-1_: UPDATE table SET HostName='host_Thread-1' where OID = 1 

這意味着兩個SELECT FOR UPDATE被執行(一個線程),但只有一個更新被執行,並且thread_1主機被持久化。它更好,但它是預期的行爲?

感謝

8

你是超級困惑,但至少事情看起來你的編輯後好。有多種方法可以做到這一點,但我已經找到了最好的辦法就是實際使用JDBC的ResultSet.update*方法:

首先,你需要與ResultSet.CONCUR_UPDATABLE說法準備SELECT ... FOR UPDATE聲明,就像這樣:

ps = conn.prepareStatement(query, 
          ResultSet.TYPE_FORWARD_ONLY, 
          ResultSet.CONCUR_UPDATABLE); 

然後,你必須使用ResultSet實際更新表:

if(rs.next()) 
{ 
    rs.updateString(columnIndex, "new_hostname"); 
    rs.updateRow(); 
} 

第三,你可能需要使用一個交易,我可以在您的更新見。希望DbUtil.close方法不會拋出任何異常,檢查null等。另外,如果你的方法變得更復雜,那麼你也應該有回滾邏輯。

出於任何原因,您不應該修改my.ini