Одновременно извлекайте (выбирайте) или создавайте (вставляйте) новую строку в общий SQL без конфликтов - PullRequest
7 голосов
/ 08 марта 2011

У меня есть система, которая имеет сложный первичный ключ для взаимодействия с внешними системами, и быстрый, непрозрачный первичный ключ для внутреннего использования. Например: внешний ключ может быть составным значением - что-то вроде (имя (varchar), фамилия (varchar), почтовый индекс (char)), а внутренний ключ будет целым числом («идентификатор клиента»).

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

Очевидно, что если у меня есть только один клиент, говорящий с базой данных одновременно, это нормально. SELECT customer_id FROM customers WHERE given_name = 'foo' AND ..., затем INSERT INTO customers VALUES (...), если я не найду значение. Но, если есть потенциально много запросов, поступающих от внешних систем одновременно, и многие из них могут прийти к ранее не услышанному клиенту сразу, есть условие состязания, когда несколько клиентов могут попытаться INSERT новая строка.

Если бы я модифицировал существующую строку, это было бы легко; сначала просто SELECT FOR UPDATE, чтобы получить соответствующую блокировку на уровне строк, прежде чем UPDATE. Но в этом случае у меня нет строки, которую можно заблокировать, потому что строка еще не существует!

Я уже придумал несколько решений, но у каждого из них есть несколько довольно существенных проблем:

  1. Перехватите ошибку на INSERT, повторите всю транзакцию сверху. Это является проблемой, если в транзакции участвует десяток клиентов, особенно если во входящих данных каждый раз речь идет об одних и тех же клиентах в разном порядке. Можно застрять во взаимно рекурсивных тупиковых циклах, где конфликт возникает каждый раз с другим клиентом. Вы можете смягчить это с экспоненциальным временем ожидания между попытками повторной попытки, но это медленный и дорогой способ справиться с конфликтами. Кроме того, это немного усложняет код приложения, так как все должно быть перезапущено.
  2. Использовать точки сохранения. Начните точку сохранения до SELECT, поймайте ошибку на INSERT, а затем откатитесь до точки сохранения и снова SELECT. Точки сохранения не являются полностью переносимыми, а их семантика и возможности немного различаются между базами данных; самое большое различие, которое я заметил, заключается в том, что иногда они, кажется, гнездятся, а иногда нет, поэтому было бы неплохо, если бы я мог их избежать. Впрочем, это только смутное впечатление - неточно? Стандартизированы ли точки сохранения или, по крайней мере, практически соответствуют? Кроме того, точки сохранения затрудняют параллельное выполнение операций в одной и той же транзакции , потому что вы не сможете точно определить, сколько работы вы будете откатывать, хотя я понимаю, что мне просто нужно жить с этим.
  3. Получите некоторую глобальную блокировку, например блокировку на уровне таблицы, используя инструкцию LOCK ( oracle mysql postgres ). Это, очевидно, замедляет эти операции и приводит к большому количеству конфликтов блокировок, поэтому я предпочел бы избежать этого.
  4. Получите более детальную, но зависящую от базы данных блокировку. Я знаком только с способом Postgres сделать это , который совершенно точно не поддерживается в других базах данных (функции даже начинаются с "pg_"), так что опять же это проблема переносимости. Кроме того, способ postgres сделать это потребовал бы от меня преобразования ключа в пару целых чисел, которые он может не вписаться аккуратно. Есть ли лучший способ приобрести замки для гипотетических объектов?

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

Ответы [ 3 ]

3 голосов
/ 08 марта 2011

Мне непонятно, почему вы не можете использовать INSERT IGNORE, который будет работать без ошибок, и вы сможете проверить, произошла ли вставка (измененные записи). Если вставка «терпит неудачу», то вы знаете, что ключ уже существует, и вы можете сделать SELECT. Вы можете сначала сделать ВСТАВКУ, затем ВЫБРАТЬ.

В качестве альтернативы, если вы используете MySQL, используйте InnoDB, который поддерживает транзакции. Это упростит откат.

1 голос
/ 08 марта 2011

Выполнение операций «поиска или создания» каждого клиента в режиме автоматической фиксации до и после основной транзакции с несколькими клиентами.

0 голосов
/ 08 марта 2011

WRT, генерирующий непрозрачный первичный ключ, существует ряд опций, например, использовать guid или (по крайней мере, с Oracle) таблицу последовательности.WRT гарантирует, что внешний ключ уникален, примените уникальное ограничение на столбец.Если вставка не удалась из-за того, что ключ существует, повторите попытку извлечения.Вы можете использовать вставку там, где ее нет, а где нет. Используйте хранимую процедуру, чтобы уменьшить количество циклов и повысить производительность.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...