Может ли эта INSERT вызывать проблемы с блокировкой / параллелизмом? - PullRequest
1 голос
/ 05 февраля 2011

Стремясь избежать автоматических порядковых номеров и тому подобного в той или иной причине в этой конкретной базе данных, я подумал, может ли кто-нибудь увидеть какие-либо проблемы с этим:

INSERT INTO user (label, username, password, user_id)
SELECT 'Test', 'test', 'test', COALESCE(MAX(user_id)+1, 1) FROM user;

Я использую PostgreSQL (но также пытаюсь быть максимально независимым от базы данных) ..

EDIT: Есть две причины, по которым я хочу сделать это.

  • Сохранение зависимости от любого конкретного уровня СУБД.
  • Не нужно беспокоиться об обновлении последовательностей, если данные пакетно обновляются в центральной базе данных.

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

EDIT-2: Идея, с которой я играю, состоит в том, что каждая таблица в базе данных имеет созданный человеком SiteCode как часть их ключа, поэтому у нас всегда есть составной ключ. Это эффективно разделяет данные на SiteCode и позволяет брать данные с определенного сайта и помещать их в другое место (очевидно, в той же структуре базы данных). Например, это позволит создавать резервные копии различных рабочих сайтов в одной центральной базе данных, но также позволит этой центральной базе данных иметь рабочие сайты, использующие ее. Я все еще могу использовать последовательности, но это кажется грязным. Фактическая вставка выглядела бы так:

INSERT INTO user (sitecode, label, username, password, user_id)
SELECT 'SITE001', 'Test', 'test', 'test', COALESCE(MAX(user_id)+1, 1)
FROM user
WHERE sitecode='SITE001';

Если это имеет смысл .. Я делал нечто подобное раньше, и он работал нормально, однако центральная база данных в этом случае была никогда работоспособной (это был скорее способ централизованного просмотра / анализа данных), поэтому ей не нужно было генерировать идентификаторы .

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

Ну что ж, вернемся к чертежной доске!

Ответы [ 6 ]

2 голосов
/ 05 февраля 2011

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

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

Если вам нужно быть независимым от СУБД, то создайте уровень абстракции, который использует последовательности для тех СУБД, которые их поддерживают (Posgres, Oracle, Firebird, DB2, Ingres, Informix, ...) и самописанный генератор для этих это не так.

Попытка создать систему, отличную от СУБД, просто означает, что она будет работать одинаково медленно на всех системах, если вы не используете преимущества каждой СУБД.

2 голосов
/ 05 февраля 2011

Есть пара моментов:

  • Postgres использует Multi-Version Concurrency Control (MVCC), поэтому читатели никогда не ждут писателей и наоборот. Но, конечно, при каждой записи происходит сериализация. Если вы собираетесь загрузить большую часть данных в систему, посмотрите на команду COPY. Это намного быстрее, чем запуск большого тампона INSERT операторов.
  • На MAX (user_id) можно ответить с помощью индекса, и, вероятно, так оно и есть, если в столбце user_id есть индекс. Но проблема real заключается в том, что если две транзакции начнутся одновременно, они увидят одинаковое значение MAX(user_id). Это приводит меня к следующему пункту:
  • Канонический способ обработки чисел, таких как user_id, заключается в использовании SEQUENCE. По сути, это место, где вы можете получить следующий идентификатор пользователя. Если вы действительно беспокоитесь о производительности при создании следующего порядкового номера, вы можете сгенерировать пакет из них для каждого потока, а затем запрашивать новый пакет только тогда, когда он исчерпан (иногда это называется последовательностью HiLo).
  • Возможно, вы хотите, чтобы user_id был упакован аккуратно и растущим числом, но я думаю, что вы должны попытаться избавиться от этого. Причина в том, что удаление user_id в любом случае создаст дыру. Так что я бы не сильно волновался, если бы последовательности не увеличивались строго.
2 голосов
/ 05 февраля 2011

Да, я вижу огромную проблему. Не делай этого.

Несколько подключений могут получить ТОЧНЫЙ ЖЕ идентификатор в то же время. Я собирался добавить «под нагрузкой», но это даже не нужно - просто нужно правильное время между двумя запросами.

Чтобы избежать этого, вы можете использовать транзакции или механизмы блокировки или уровни изоляции, специфичные для каждой БД, но как только мы дойдем до этого этапа, вы также можете использовать специфичную для dbms последовательность / идентификатор / автономер и т. Д.

EDIT

Для question edit2 нет причин бояться пробелов в user_id, поэтому у вас есть одна последовательность для всех сайтов. Если с пропусками все в порядке, некоторые варианты

  • использовать гарантированные операторы обновления, такие как (в SQL Server)

обновить tblsitesequenceno установить @nextnum = nextnum = nextnum + 1

Несколько абонентов этого утверждения гарантированно получают уникальный номер.

  • использовать одну таблицу, которая создает идентификатор / последовательность / автонумера (специфично для дБ)

Если у вас вообще не может быть пробелов, рассмотрите возможность использования механизма транзакций, который ограничит доступ во время выполнения запроса max (). Либо так, либо используйте множество (последовательностей / таблиц с идентичными столбцами / таблиц с автономным номером), которыми вы манипулируете, используя динамический SQL, используя ту же технику для одной последовательности.

1 голос
/ 05 февраля 2011

Ваша цель хорошая.Избегать столбцов IDENTITY и AUTOINCREMENT означает избегать целого множества проблем администрирования.Вот только один пример из множества.

  • Однако большинство респондентов в SO не оценят его, популярным (в отличие от технического) ответом является «всегда прикреплять столбец Id AUTOINCREMENT ко всему, что движется».

  • Следующий последовательный номер в порядке, все поставщики его оптимизировали.

  • Пока этот код находится внутри транзакции, как и должно быть, два пользователя не получат одинаковое значение MAX()+1.Существует концепция «Уровень изоляции», которую необходимо понимать при кодировании транзакций.

  • Отход от user_id и переход к более значимому ключу, например ShortName или State плюсUserNo еще лучше (первый распространяет разногласия, второй вообще исключает разногласия следующего уровня, относящиеся к системам большого объема).

  • Что обещает MVCC и что на самом деле обеспечиваетЭто две разные вещи.Просто зайдите в сеть или поищите SO, чтобы увидеть сотни проблем, связанных с PostcreSQL / MVCCВ области компьютеров действуют законы физики, ничто не является бесплатным.MVCC хранит частные копии всех затронутых строк и разрешает коллизии на конце Транзакции, что приводит к гораздо большему количеству откатов.Принимая во внимание, что 2PL блокируется в начале Транзакции и ожидает без массового хранения копий.

    • большинство людей с фактическим опытом MVCC нерекомендую его для систем с высокой конкуренцией и большим объемом.

Первый пример кода блока в порядке.

Согласно комментариям, этот элемент больше неприменяется: Во втором примере блока кода есть проблема.«SITE001» - это не составной ключ, это составной столбец.Не делайте этого, разделите «SITE» и «001» на два отдельных столбца.И если «САЙТ» является фиксированным, повторяющимся значением, его можно устранить.

0 голосов
/ 05 февраля 2011

Я согласен с Максимько, но не потому, что мне не нравятся последовательности или автоинкрементные числа, так как они имеют свое место. Если вам нужно, чтобы значение было уникальным во всех ваших «различных рабочих сайтах», т. Е. Не только в пределах одного экземпляра базы данных, глобальный уникальный идентификатор является надежным и простым решением.

0 голосов
/ 05 февраля 2011

Разные пользователи могут иметь один и тот же user_id, параллельные операторы SELECT будут видеть один и тот же MAX (user_id).

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

CREATE TABLE my_sequence(id INT);

BEGIN;
UPDATE my_sequence SET id = COALESCE(id, 0) + 1;
INSERT INTO 
  user (label, username, password, user_id)
SELECT 'Test', 'test', 'test', id FROM my_sequence;
COMMIT;
...