Лучший способ получить следующий идентификационный номер без «идентичности» - PullRequest
8 голосов
/ 15 апреля 2009

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

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

select @id=ISNULL(max(recid)+1,1) from subscriber

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

begin transaction
    declare @id as int
    select @id=ISNULL(max(recid)+1,1) from subscriber WITH (HOLDLOCK, TABLOCK)
    select @id
    WAITFOR DELAY '00:00:01'
    insert into subscriber (recid) values (@id)
commit transaction
select * from subscriber

в двух разных окнах в SQL Management Studio, и одна транзакция всегда уничтожается как жертва тупика.

Я тоже сначала попробовал SET TRANSACTION ISOLATION LEVEL SERIALIZABLE с тем же результатом ...

Любые хорошие предложения о том, как я могу обеспечить получение следующего идентификатора и использовать его, не рискуя, что кто-то еще (или я!) Попадет под охоту?

Извините, что не упомянул об этом раньше, но это сервер SQL 2000, поэтому я не могу использовать такие вещи, как FOR UPDATE и OUTPUT

ОБНОВЛЕНИЕ : Это решение, которое сработало для меня:

BEGIN TRANSACTION
    DECLARE @id int

    SELECT  @id=recid
    FROM    identities WITH (UPDLOCK, ROWLOCK)
    WHERE table_name = 'subscriber'

    waitfor delay '00:00:06'

    INSERT INTO subscriber (recid) values (@id)

    UPDATE identities SET recid=recid+1 
    WHERE table_name = 'subscriber'

COMMIT transaction

select * from subscriber

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

Спасибо Quassnoi за ответ и всем вам, ребята, которые внесли свой вклад! Отлично!

Ответы [ 4 ]

10 голосов
/ 15 апреля 2009

Создать другую таблицу:

t_identity (id INT NOT NULL PRIMARY KEY CHECK (id = 1), value INT NOT NULL)

одной строкой, заблокируйте эту строку и увеличивайте value на единицу каждый раз, когда вам нужно IDENTITY.

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

UPDATE  t_identity
SET     value = value + 1
OUTPUT  INSERTED.value

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

SELECT  value
FROM    t_identity WITH (UPDLOCK, ROWLOCK)

Это заблокирует таблицу до конца транзакции.

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

4 голосов
/ 15 апреля 2009

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

Обновление : В зависимости от частоты вставок (и количества существующих строк e ) вы можете задать новые значения IDENTITY на e + x , где x достаточно велико. Это позволит избежать конфликта с устаревшими вставками. Печальное решение, конечно, несовершенное, но над чем подумать?

3 голосов
/ 15 апреля 2009

РЕДАКТИРОВАТЬ это в основном метод, заданный @Quassnoi, я просто реализую его в цикле, чтобы вы могли запустить его одновременно с несколькими окнами, чтобы убедиться, что он отлично работает.

Настройка:

создать существующую таблицу пользователя:

create table Subscriber
(
recid  int not null primary key
)

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

CREATE TABLE SubscriberIDs
(
SubscriberID int
)
insert into SubscriberIDs values (0) --row must exist first

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

declare @idtable table --will hold next ID to use
(
id int
)
declare @x  int
declare @y  int
set @x=0
while @x<5000 --set up loop
begin
    set @x=@x+1
    begin transaction
    --get the next ID to use, lock out other users
    UPDATE SubscriberIDs
        SET SubscriberID= SubscriberID+ 1
        OUTPUT  INSERTED.SubscriberID
        INTO @idtable
    --capture the next id from temp table variable
    select @y=id from @idtable
    --print @y
    --use the next id in the actual table
    insert into subscriber values (@y)

    commit
    --print @x
    waitfor delay '00:00:00.005'
end --while

-------------------------------------------- -------------------
РЕДАКТИРОВАТЬ Вот моя первоначальная попытка, которая в конечном итоге приведет к появлению некоторых тупиковых ситуаций при запуске в цикле и в нескольких окнах одновременно. Вышеуказанный метод всегда работает. Я перепробовал все комбинации транзакций с помощью (holdlock) и установил сериализуемый уровень изоляции транзакций и т. Д., Но не смог заставить его работать так же, как и описанный выше метод.

настроено:

create table subscriber
(
recid  int not null primary key
)

используется для захвата идентификатора:

declare @idtable table
(
id int
)

вставка:

insert into subscriber
    OUTPUT INSERTED.recid
        recid
    INTO @idtable
    SELECT ISNULL(MAX(recid),0)+1 FROM subscriber

перечислить новый идентификатор:

select * from @idtable

перечислить все идентификаторы:

select * from subscriber
0 голосов
/ 17 апреля 2009

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

Кроме того, вы получаете идентификатор, а затем используете его в двух отдельных операторах, тогда как вы можете сделать все это в одном решении:

set transaction isolation level serializable
begin transaction
    insert into subscriber (recid) 
       SELECT (select ISNULL(max(recid)+1,1) from subscriber)
commit transaction
select * from subscriber

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

...