Тупик параллельной транзакции SQL / Java - PullRequest
0 голосов
/ 28 марта 2020

У меня есть метод boolean addIntegerColumnToDatabases(String tableName, String columnName, Connection ... conns), где у меня есть коллекция SQL -Connections.

Для каждого SQL -Connection я выполняю запрос на обновление схемы

BEGIN
  ALTER TABLE <b>tableName</b> ADD COLUMN <b>columnName</b> int4
COMMIT

Поскольку этот метод должен быть ACID, мне нравится делать это параллельно.

Например, у меня есть два соединения (C1, C2):

C1: НАЧАЛО

C2: НАЧАЛО

C1: ALTER TABLE tableName ADD COLUMN columnName int4

C2: ALTER TABLE tableName ADD COLUMN columnName int4

C1 : COMMIT

C2: COMMIT

Это код:

Statement[] s = new Statement[conns.length];
int i =0;
for(Connection c:conns) {
  c.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
  c.setAutoCommit(false);
  s[i++]=c.createStatement();
}
for(Statement st:s) {
  st.executeUpdate("ALTER TABLE "+tableName+" ADD COLUMN "+columnName+" int4;");
}
for(Connection c:conns) {
  c.commit();
}
return true;

Это работает, если соединения находятся в разных базах данных.

Проблема

Если C1 и C2 подключены к одной базе данных, C2 ожидает фиксации C1 на Postgres -Side, а C1 ждет C2 на Java -Side. Вуаля, у нас тупик.

У меня нет 100% шанса проверить, подключены ли к одной и той же системе базы данных из-за таких проблем, как кластеризация / балансировка, ipv4 / 6 и dns-проблемы.

Вопрос

Как удостовериться в return false;, если выполнение имеет два соединения с одной и той же базой данных без выполнения какого-либо изменения схемы?

Ответы [ 3 ]

0 голосов
/ 28 марта 2020

Для этого можно использовать консультативные блокировки .

Консультативная блокировка использует произвольное число, предоставленное абонентом, и существует до тех пор, пока не будет явно освобождена или соединение не будет закрыто. Вы можете преобразовать имя таблицы в ее oid и использовать это число в качестве идентификатора блокировки.

Что-то вроде (без обработки ошибок или очистки!)

ResultSet st.executeQuery("select pg_try_advisory_lock('" + tableName + "'::regclass::oid::bigint)");
rs.next();
boolean locked = rs.getBoolean(1);
if (locked) {
  // do your migration
  .....

  // once you are done with the table, release the lock immediately
  // alternatively keep it open and it will be released automatically 
  // when the connection is closed
  st.execute("select pg_advisory_unlock('" + tableName + "'::regclass::oid::bigint)");
} else {
  // handle the conflict
}

Возможно, вы захотите перенести эту функциональность в собственный метод.


В качестве альтернативы вы можете просто попытаться получить одну блокировку с каким-либо жестко заданным номером в начале миграции (независимо от таблицы, которую нужно изменить).

0 голосов
/ 31 марта 2020

Хм, у меня есть другая идея.

Я мог бы сначала измерить, какое время будет ожидать для каждой отдельной базы данных, просто выполнив ROLLBACK вместо COMMIT.

Допустим,

  1. ? - это одноразовое потребление времени между BEGIN и COMMIT одной базы данных, а
  2. ?? - это количество всех временных затрат.
  3. ?? - это максимальное, самое высокое значение из всех измеренных временных затрат max (??) .

Затем я установлю тайм-аут Watchdog на

2 * ??

Если сторожевой таймер не работает, у нас есть дубликаты соединений с базой данных, и мы должны явно вернуть false.

0 голосов
/ 28 марта 2020

Используйте справочную таблицу database_instance, заполненную GUID.

Перед запросом обновления схемы, получите GUID и заблокируйте его в локальном словаре.

Или просто заблокируйте таблица локально, независимо от того, в какой базе данных она находится:

var tablename = new object();
lock(tablename)
{
   <your query>
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...