Параллельный доступ к базе данных - предотвращение получения двумя пользователями одного и того же значения - PullRequest
6 голосов
/ 19 марта 2012

У меня есть таблица с порядковыми номерами (например, номера счетов или студенческие идентификаторы).

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

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

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

Редактировать : Вот что у меня есть:

USE [master]
GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[sp_GetNextOrderNumber]
AS
BEGIN
    BEGIN TRAN

DECLARE @recentYear INT
DECLARE @recentMonth INT
DECLARE @recentSequenceNum INT

-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;

    -- get the most recent numbers
SELECT @recentYear = Year, @recentMonth = Month, @recentSequenceNum = OrderSequenceNumber
FROM dbo.OrderNumbers 
WITH (XLOCK)
WHERE Id = (SELECT MAX(Id) FROM dbo.OrderNumbers)

    // increment the numbers
IF (YEAR(getDate()) > IsNull(@recentYear,0))
    BEGIN
        SET @recentYear = YEAR(getDate());
        SET @recentMonth = MONTH(getDate());
        SET @recentSequenceNum = 0;
    END
ELSE
    BEGIN
        IF (MONTH(getDate()) > IsNull(@recentMonth,0))
            BEGIN
                SET @recentMonth = MONTH(getDate());
                SET @recentSequenceNum = 0;
            END
        ELSE
            SET @recentSequenceNum = @recentSequenceNum + 1;
    END 

-- insert the new numbers as a new record   
INSERT INTO dbo.OrderNumbers(Year, Month, OrderSequenceNumber)
VALUES (@recentYear, @recentMonth, @recentSequenceNum)

COMMIT TRAN
END

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

Редактировать 2 : Добавлено WITH(XLOCK) для блокировки таблицы до завершения транзакции. Я не собираюсь выступать здесь. До тех пор, пока я не получу повторяющиеся записи и не произойдет взаимоблокировки, это должно работать.

Ответы [ 3 ]

6 голосов
/ 19 марта 2012

Вы знаете, что SQL Server делает это для вас, верно? Вы можете указать столбец идентификаторов, если вам нужен порядковый номер или вычисляемый столбец, если вам нужно вычислить новое значение на основе другого.

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

Прочитайте эту ссылку , чтобы узнать об уровне изоляции транзакции

просто убедитесь, что период "блокировки" как можно меньше

3 голосов
/ 19 марта 2012

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

create table [Counter]
(
   LastNumber int
)

и инициализировать его одной строкой:

insert into [Counter] values(0)

Пример таблицы счетов-фактур:

create table invoices 
(
  InvoiceID int identity primary key, 
  Number varchar(8), 
  InvoiceDate datetime
)

Хранимая процедура LastNumber сначала обновляет строку счетчика, а затем извлекает значение.Поскольку значение является целым числом, оно просто возвращается как возвращаемое значение процедуры;в противном случае потребуется выходной столбец.Процедура принимает в качестве параметра число следующих чисел для выборки;вывод является последним числом.

create proc LastNumber (@NumberOfNextNumbers int = 1)
as
begin
   declare @LastNumber int

   update [Counter] 
      set LastNumber = LastNumber + @NumberOfNextNumbers -- Holds update lock
   select @LastNumber = LastNumber 
     from [Counter]
   return @LastNumber 
end

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

create trigger InvoiceNumberTrigger on Invoices
after insert
as
   set NoCount ON

   declare @InvoiceID int
   declare @LastNumber int
   declare @RowsAffected int

   select @RowsAffected = count(*)
     from Inserted
   exec @LastNumber = dbo.LastNumber @RowsAffected

   update Invoices
    -- Year/month parts of number are missing
      set Number = right ('000' + ltrim(str(@LastNumber - rowNumber)), 3)
     from Invoices
        inner join
        ( select InvoiceID, 
                 row_number () over (order by InvoiceID desc) - 1 rowNumber
            from Inserted
        ) insertedRows
          on Invoices.InvoiceID = InsertedRows.InvoiceID

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

Пример использования:

insert into invoices (invoiceDate) values(GETDATE())

Поскольку значение числового столбца генерируется автоматически, его необходимо перечитать.Я считаю, что у EF есть условия для этого.

0 голосов
/ 19 марта 2012

Способ обработки этого в SQL Server заключается в использовании табличной подсказки UPDLOCK в одной транзакции.

Например:

  INSERT 
    INTO MyTable (
         MyNumber ,
         MyField1 )
  SELECT IsNull(MAX(MyNumber), 0) + 1 ,
         "Test"
    FROM MyTable WITH (UPDLOCK)

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

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