Как структурировать эту сделку?

У нас есть веб-приложение на основе ASP.NET/MSSQL, которое генерирует заказы с последовательными номерами заказов.

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

  1. ВЫБЕРИТЕ MAX(order_number) FROM order_table, назовите это max_order_number
  2. установить new_order_number = max_order_number + 1
  3. ВСТАВЬТЕ новую запись заказа с этим new_order_number (это просто поле в записи заказа, а не ключ базы данных)

Если я включу вышеуказанные 3 шага в одну транзакцию, предотвратит ли это создание повторяющихся номеров заказов, если два клиента сохранят новый заказ одновременно? (И допустим, система в конечном итоге находится на веб-ферме с несколькими серверами IIS и одним сервером MSSQL).

Я хочу, чтобы два клиента не выбрали один и тот же MAX(order_number) из-за параллелизма где-то в системе.

Какой уровень изоляции следует использовать? Спасибо.


person royappa    schedule 25.07.2012    source источник
comment
Столбец идентификаторов, как упоминалось ниже, был бы лучшим решением. Однако вам разрешено использовать только один столбец идентификаторов для каждой таблицы. Если у вас уже есть другой столбец в качестве идентификатора, вы можете подумать о том, чтобы сделать order_number просто составным элементом этого столбца идентификатора.   -  person Mike Guthrie    schedule 25.07.2012


Ответы (4)


Использование идентичности, безусловно, лучшая идея. Я создаю все свои таблицы следующим образом:

CREATE TABLE mytable (
    mytable_id int identity(1, 1) not null primary key,
    name varchar(50)
)

Флаг «удостоверение» означает «Разрешить SQL Server назначить этот номер для меня». (1, 1) означает, что идентификационные номера должны начинаться с 1 и увеличиваться на 1 каждый раз, когда кто-то вставляет запись в таблицу. Not Null означает, что никто не должен иметь права вставлять в этот столбец значение null, а «первичный ключ» означает, что мы должны создать кластеризованный индекс для этого столбца. С помощью таблицы такого типа вы можете вставить свою запись следующим образом:

-- We don't need to insert into mytable_id column; SQL Server does it for us!
INSERT INTO mytable (name) VALUES ('Bob Roberts')

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

-- Begin a transaction - this means everything within this region will be 
-- executed atomically, meaning that nothing else can interfere.
BEGIN TRANSACTION
    DECLARE @id bigint

    -- Retrieves the maximum order number from the table
    SELECT @id = MAX(order_number) FROM order_table

    -- While you are in this transaction, no other queries can change the order table,
    -- so this insert statement is guaranteed to succeed
    INSERT INTO order_table (order_number) VALUES (@id + 1)

-- Committing the transaction releases your lock and allows other programs 
-- to work on the order table
COMMIT TRANSACTION

Просто имейте в виду, что объявление вашей таблицы столбцом первичного ключа идентификатора делает все это за вас автоматически.

person Ted Spence    schedule 25.07.2012
comment
Ваш пример транзакции — это именно то, что я хотел подтвердить, будут ли SELECT и INSERT выполняться атомарно вместе с настройками по умолчанию или мне нужно будет указать определенный уровень изоляции. Что касается полей идентификации, мы их уже часто используем (я думаю о них как об автонумерации, выдавая мой фон MySQL), но этот порядковый номер должен добавляться к существующей последовательности. Интересно, можно ли изменить начальную точку поля IDENTITY. - person royappa; 25.07.2012
comment
Да, вы можете использовать IDENTITY(1234, 1) — это означает, что нужно начинать с 1234 и увеличивать на единицу. - person Ted Spence; 26.07.2012

Почему бы просто не использовать Идентификатор в качестве номера заказа?

Редактировать:
Насколько мне известно, вы можете сделать текущий столбец order_number идентификатором (возможно, вам придется сбросить начальное значение, я давно этого не делал). Вы можете провести несколько тестов.
Вот хорошее чтение о том, что на самом деле происходит, когда вы меняете столбец на Identity в SSMS. Автор упоминает, что это может занять некоторое время, если таблица уже содержит миллионы строк.

person Marcus    schedule 25.07.2012
comment
Обновлено в ответ на предложения IDENTITY: эта система должна поддерживать обратную совместимость с устаревшей системой, из которой уже импортированы тысячи номеров заказов, и эти номера заказов передаются другим поставщикам, где они сканируются с помощью штрих-кода и т. д. Таким образом, мы должны сохранить существующую последовательность заказов после запуска нашей новой системы. Можно ли изменить подход IDENTITY, чтобы он соответствовал этому сценарию? - person royappa; 25.07.2012
comment
Вы можете создать свое поле идентификации, а затем «повторно заполнить» его: sqlserver2000.databases.aspfaq.com/ - person Paddy; 25.07.2012
comment
Ах, оба подхода выше могут работать. У нас есть несколько часов, чтобы сделать преобразование при запуске. - person royappa; 25.07.2012

Риск заключается в том, что два процесса выбирают MAX(order_number) до того, как один из них вставит новый заказ. Более безопасный способ сделать это за один шаг:

INSERT INTO order_table
(order_number, /* other fields */)
VALUES
( (SELECT MAX(order_number)+1 FROM order_table ) order_number,
  /* other values */
)
person D Stanley    schedule 25.07.2012
comment
Хорошее предложение, но при сохранении одной формы может быть создано НЕСКОЛЬКО заказов. Итак, логика такова (я должен был объяснить лучше), сначала выбрать MAX, а затем, если вы создаете 3 заказа, вы делаете 3 вставки с MAX+1, MAX+2, MAX+3. - person royappa; 25.07.2012

Я согласен с G_M; используйте поле Identity. Когда вы добавляете свою запись, просто

INSERT INTO order_table (/* other fields */)
VALUES (/* other fields */) ; SELECT SCOPE_IDENTITY()

Возвращаемым значением из Scope Identity будет номер вашего заказа.

person Dan Pichelman    schedule 25.07.2012