2016-06-27 6 views
0

私はConcurrentMap.putIfAbsentのように動作するSQL DBにメソッドを書き込もうとしています。ここで、 'value'はキーとして、 'id'は値として機能します。このメソッドの主な制約は、値をテーブル全体で一意に保つことです。oracle TRANSACTION_SERIALIZABLEレベルを理解しようとしています

以下はこの方法の例です。 sync.yield()の呼び出しは、他のスレッドに制御を渡します。必要な並列スレッド実行を実現するために追加されました。

import java.sql.*; 
import java.util.concurrent.atomic.AtomicInteger; 


public class Main { 

private static final String USER = ""; 
private static final String PASS = USER; 
private static final String URL = "jdbc:oracle:thin:@192.168.100.160:1521:main"; 

private static final AtomicInteger id = new AtomicInteger(); 
private static final Sync sync = new Sync(1); 

static Connection getConnection() throws Exception { 
    Class.forName("oracle.jdbc.driver.OracleDriver"); 
    Connection c = DriverManager.getConnection(URL, USER, PASS); 
    c.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); 
    return c; 
} 

static long putIfAbsent(String value) throws Exception { 
    Connection c = getConnection(); 

    PreparedStatement checkSt = c.prepareStatement("SELECT id from test WHERE value = ?"); 
    checkSt.setString(1, value); 
    ResultSet rs = checkSt.executeQuery(); 

    if (rs.next()) 
     return rs.getLong(1); 

    System.out.println(Thread.currentThread() + " did not find value"); 

    sync.yield(); 

    long id = getId(); 
    System.out.println(Thread.currentThread() + " prepare to insert value with id " + id); 
    PreparedStatement updateSt = c.prepareStatement("INSERT INTO test VALUES (?, ?)"); 
    updateSt.setLong(1, id); 
    updateSt.setString(2, value); 

    updateSt.executeQuery(); 
    c.commit(); 
    c.close(); 

    return id; 
} 

public static void main(String[] args) { 

    Runnable r =() -> { 
     try { 
      System.out.println(Thread.currentThread() + " commit success and return id = " + putIfAbsent("val")); 
      sync.yield(); 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    }; 

    Thread t1 = new Thread(r); 
    Thread t2 = new Thread(r); 

    t1.start(); 
    t2.start(); 
} 

static long getId() { 
    return id.incrementAndGet(); 
} 

} 

私は空のテーブルの上にmainメソッドを実行すると、私は、このコンソール出力を得る:

Thread[Thread-0,5,main] did not find value 
Thread[Thread-1,5,main] did not find value 
Thread[Thread-0,5,main] prepare to insert value with id 1 
Thread[Thread-0,5,main] commit success and return id = 1 
Thread[Thread-1,5,main] prepare to insert value with id 2 
Thread[Thread-1,5,main] commit success and return id = 2 

私は最初の5行を説明することができます。しかし、第六はできません。 スレッド1が更新を実行するとき、それはSELECT id from test WHERE value = ?に空の結果があることに依存します。この結果は、現在のDB状態と一致しません。だから、私はORA-08177: Cannot serialize access for this transactionを期待しています。

私は(それは、スレッドのオブジェクト上のリンク参照を保持する)同期クラスのこのimpementationsを使用しています:表を作成するための

import java.util.ArrayDeque; 
import java.util.HashSet; 
import java.util.Queue; 
import java.util.Set; 

public class Sync { 
    private final Object lock = new Object(); 
    private final Queue<Thread> sleepingTh = new ArrayDeque<>(); 
    private final Set<Thread> activeTh = new HashSet<>(); 

    private final int threads; 

    public Sync(int threads) { 
     this.threads = threads; 
    } 

    public void yield() { 
     final Thread ct = Thread.currentThread(); 

     synchronized (lock) { 
      sleepingTh.add(ct); 
      activeTh.remove(ct); 

      if (sleepingTh.size() > threads) { 
       Thread t = sleepingTh.poll(); 
       activeTh.add(t); 
       lock.notifyAll(); 
      } 

      while (!activeTh.contains(ct)) { 
       try { 
        lock.wait(); 
       } catch (InterruptedException e) { 
       } 
      } 
     } 
    } 

    public void wakeUpAll() { 
     synchronized (lock) { 
      activeTh.addAll(sleepingTh); 
      sleepingTh.clear(); 
      lock.notifyAll(); 
     } 
    } 
} 

声明:

create table test(
id number(16), 
value varchar(50) 
); 

を私はjdk1.8.0_60、OracleのJDBCを使用10.2.0.4.0、およびOracle DB 11g2

+0

正確な質問は何ですか? –

+0

ORA-08177:このトランザクションのアクセスをシリアル化できません。 – osseum

答えて

0

説明書==>clickによると、

SERIALIZABLE分離レベル
..............
..............
Oracle Databaseは、シリアライズを許可します他のトランザクションによって行が変更された場合にのみ、 行を変更することができます( )。

ORA-08177:このトランザクションのアクセスをシリアル化できません

シリアライザブルトランザクションが更新しようとしたり 削除データがシリアライザブルトランザクションが開始された 後にコミット別のトランザクションによって変更されたとき データベース はエラーを生成します

コードはINSERT文のみ実行されます。
またはのデータが別の取引で変更されたため、ORA-08177が発生しないように更新しようとしていません。


---- EDIT --------------


あなたはアドバイスを与えることができない、私はこの方法をどのように書き換えることができますか?

valueの列にユニークなcontaraintを作成してください。
コードではまっすぐにINSERTステートメントを実行します。
成功した場合 - つまり、行がまだ存在していないことを意味します。
失敗した場合(重複キー例外) - 行がすでに存在し、この場合は単純なエラーを無視します。

SQL-92ではこの動作が可能ですか?

はい、もちろんです。
SQL-92の詳細については、このリンクを参照して、3つだけリード現象を定義:トランザクションが別によって変更された行からデータを読み出すために許可されている場合Isolation (database systems)

  • ダーティは(別名コミットされていない依存性)を読み取るが発生します実行中のトランザクションで、まだコミットされていません。
  • 非反復は 取引の過程で、行が二回検索され、行内の値が読み出し間で異なる 場合は、発生読み取ります。
  • ファントムは、取引の過程では、2つの 同じクエリが実行されたとき、が発生し、行のコレクションは、2番目のクエリによって に返される最初の異なる読み出します。

シリアライズ可能な分離レベルでなし上記現象のが発生することがあります。 トランザクションは、他のトランザクションからの変更を見ることができません。
あなたのコードでは、これは是非とも真実です。 は、このアイソレーションレベルでファントム現象またはダーティリード現象が発生しないため、他のセッションで挿入された行をに見ることはできません。

+0

あなたはアドバイスを与えることができます、どのように私は方法を書き換えることができますか? – osseum

+0

SQL-92ではこの動作が可能ですか? – osseum

+0

答えを編集しましたのでご覧ください。 – krokodilko

関連する問題