2016-06-27 4 views
0

Я пытаюсь написать метод на базе SQL DB, который работает как ConcurrentMap.putIfAbsent, где «значение» служит ключом и «id» как значение. Основным ограничением этого метода является сохранение значения уникальным по всей таблице.Пытается понять oracle Уровень транзакции_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(); 
} 

} 

При запуске основного метода на пустом столе, я получаю этот консольный вывод:

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 

Я могу объяснить первые пять строк. Но не может шестая. Когда поток-1 выполняет обновление, полагается, что SELECT id from test WHERE value = ? имеют пустой результат. Этот результат не согласуется с текущим состоянием БД. Итак, я ожидаю 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 говорит, что:

Сериализуемый Уровень изоляции
..............
..............
Oracle Database позволяет сериализации транзакция для изменения строки , только если изменения в строке, сделанные другими транзакциями, уже были , совершенные при начале сериализации. База генерирует ошибку, когда сериализуемая транзакция пытается обновить или удалять данные изменены другая сделкой, совершившей после началась сериализуемая сделка:

ORA-08177: не удается сериализовать доступ для данной транзакции

Ваш код выполняет только INSERT заявления.
Он не пытается обновление или удалить данные, измененные другой транзакцией,
, поэтому ORA-08177 не возникает.


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


вы можете дать совет, как я могу переписать метод?

Просто создайте уникальный контайн на колонке value.
В коде просто сделайте прямо INSERT заявление.
Если это удастся - это означает, что строки еще не существует
Если сбой (дублирование ключевого исключения) - это означает, что строка уже существует и в этом случае просто игнорирует ошибку.

SQL-92 разрешает подобное поведение?

Да, конечно.
SQL-92 определяет только три явления чтения, увидеть эту ссылку для подробностей: Isolation (database systems)

  • загрязнены читать (иначе неизрасходованной зависимость) возникает, когда транзакция разрешено считывать данные из строки, которая была изменена другим запустив транзакцию и еще не совершив.
  • Не-повторяемые чтения происходит, когда в ходе операции в , строка извлекается в два раза, а значения в пределах строки различаются между читает.
  • Фантом чтения происходит тогда, когда в ходе операции, два идентичных запросы выполняются, и сбор возвращаемых строк посредством второго запроса отличается от первого.

В сериализованных уровнях изоляции не существует вышеуказанных явлений. Thats all. Сделка не может видеть любые изменения из других транзакций.
И в вашем коде это верно. Сессия не может видеть строку, вставленную другим сеансом, потому что ни феномены фантома, ни явления грязного чтения не могут возникать на этом уровне изоляции.

+0

Вы можете дать совет, как я могу переписать метод? – osseum

+0

SQL-92 допускает такое поведение? – osseum

+0

Я отредактировал ответ, пожалуйста, взгляните на него. – krokodilko