2015-12-24 4 views
1

Я работаю над программой, которая позволяет нескольким пользователям получить доступ к db (MySQL), и в разное время я получаю SQLException: Lock wait timeout exceeded.Когда вы отключаете `SELECT ... FOR UPDATE`?

соединение создается с помощью:

conn = DriverManager.getConnection(connString, username, password); 
conn.setAutoCommit(false); 

и звонки все пройти через этот бит кода:

try { 
    saveRecordInternal(record); 
    conn.commit(); 
} catch (Exception ex) { 
    conn.rollback(); 
    throw ex; 
} 

Где saveRecordInternal имеет некоторую внутреннюю логику, сохраняя данную запись. Где-то по пути является метод, который я подозреваю, что проблема:

private long getNextIndex() throws Exception { 
    String query = "SELECT max(IDX) FROM MyTable FOR UPDATE"; 
    PreparedStatement stmt = conn.prepareStatement(query); 

    ResultSet rs = stmt.executeQuery(); 
    if (rs.next()) { 
     return (rs.getLong("IDX") + 1); 
    } else { 
     return 1; 
    } 
} 

Этот метод вызывается saveRecordInternal где-то это операция, в случае необходимости. По причинам, которые в настоящее время находятся вне моего контроля, я не могу использовать индекс автоматического инкремента, и в любом случае индекс, который нужно встать, необходим для некоторой внутренней логики.

Я бы предположил, что либо conn.commit(), либо conn.rollback() назвали достаточно, чтобы освободить замок, но, видимо, это не так. Поэтому мой вопрос: должен ли я использовать stmt.close() или rs.close() внутри getNextIndex? Отпустит ли блокировку до того, как транзакция будет совершена или откатна, или просто обеспечит освобождение блокировки при вызове conn.commit() или conn.rollback()?

Есть ли что-нибудь еще, что мне не хватает/делает совершенно неправильно?

Редактировать: В момент блокировки все подключенные клиенты кажутся отзывчивыми, без запросов в настоящее время, но закрытие всех подключенных клиентов устраняет проблему. Это заставляет меня думать, что замок каким-то образом сохраняется, даже если сделка (предположительно?) Заканчивается, совершая или откатываясь назад.

ответ

0

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

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

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

+0

Да, я подумал, что где-то есть замок, вопрос в том, как он останавливается? Кажется, что все клиенты выполняют свои задачи, но блокировка остается (я объясню свой вопрос, чтобы объяснить это). – Itai

+0

Итак, что может случиться здесь: клиент выполнил некоторый запрос в базе данных, а клиент выпустил соединение. но запрос запущен в базе данных. Таким образом, даже несмотря на то, что клиент выпустил соединение, запрос все еще запущен. – Jaydatt

+0

Это не имеет смысла, потому что при закрытии клиента проблема решена, поэтому она не может быть сиротским соединением/запросом. – Itai

2

Хотя это не закрытие Statement или ResultSet - это плохая идея, но эта функция не отвечает за полученную вами ошибку. Function, getNextIndex() создает local Statement и ResultSet, но не закрывает его. Закройте те, что есть, или создайте те Statement и ResultSet объектов в saveRecordInternal() и передайте их как параметры или лучше, если они созданы в вашей начальной точке и повторно используются снова и снова.Наконец, закройте их, когда они больше не нужны (в следующем порядке - ResultSet, Statement, Connection).

Ошибка просто означает, что блокировка присутствовала на каком-то объекте DB (Connection, Table, Row и т. Д.), В то время как другой поток/процесс нуждался в этом в одно и то же время, но должен был ждать (уже заблокирован), но ждать вышло из-за более длительного чем ожидалось.

См., How to Avoid Lock wait timeout exceeded exception.?, чтобы узнать больше об этой проблеме.

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

Надеюсь, это поможет!

+0

Спасибо. Итак, если я правильно понимаю, использование 'stmt.close()' не будет освобождать блокировку до тех пор, пока транзакция не будет выполнена или не будет отменена? – Itai

+0

Да, это правильно, согласно моим знаниям. –

2

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

В вашем случае

SELECT max(IDX) FROM MyTable FOR UPDATE 

приведет к блокировке всей таблицы, но я предполагаю, что это ожидаемая логика! Вы блокируете таблицу до тех пор, пока не будет вставлена ​​новая строка, а затем отпустите ее, чтобы другие вставляли ее. Я бы проверить с:

SELECT IDX FROM MyTable FOR UPDATE Order by IDX Desc LIMIT 1 

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

Если это не так, я могу быть таймаутом блокировки из-за очень большой таблицы.

+0

Насколько велика большая? В настоящее время он имеет <10K строк и 6 столбцов (самый большой из которых является «varchar (100)», остальные - int, date или «varchar (15)») – Itai

+0

Это на самом деле очень мало, но это действительно зависит от вашей среды. Если Max (IDX) не требуется в вашем приложении, вы можете вызвать этот запрос в то время, когда вы действительно вставляете новую строку. –

+0

Еще одна вещь, которую следует учитывать в этом случае: Select Max (IDX) From MyTable FOR UPDATE фактически выполнит блокировку уровня строки для всех затронутых строк (в этом случае все). Возможно, лучше всего заблокировать всю таблицу: LOCK TABLES MyTable READ, а затем UNLOCK! –