Какой правильный шаблон для уникальных данных в столбцах? - PullRequest
0 голосов
/ 21 января 2009

У меня есть таблица [Файл], которая имеет следующую схему

CREATE TABLE [dbo].[File]
(
    [FileID] [int] IDENTITY(1,1) NOT NULL,
    [Name] [varchar](256) NOT NULL,
 CONSTRAINT [PK_File] PRIMARY KEY CLUSTERED 
(
    [FileID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

Идея состоит в том, что FileID используется в качестве ключа для таблицы, а Name - это полный путь к файлу.

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

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

Эта версия кода создаст взаимоблокировку и выдаст исключение взаимоблокировки на клиенте.

CREATE PROCEDURE [dbo].[File_Create]
    @Name varchar(256)
AS
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    BEGIN TRANSACTION xact_File_Create
    SET XACT_ABORT ON

    SET NOCOUNT ON 
    DECLARE @FileID int
    SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    IF @@ROWCOUNT=0
    BEGIN
        INSERT INTO [dbo].[File]([Name])
        VALUES (@Name)
        SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    END

    SELECT * FROM [dbo].[File]
    WHERE [FileID] = @FileID

    COMMIT TRANSACTION xact_File_Create
GO

В этой версии кода я получаю строки с одинаковыми данными в столбце Имя.

CREATE PROCEDURE [dbo].[File_Create]
    @Name varchar(256)
AS
    BEGIN TRANSACTION xact_File_Create

    SET NOCOUNT ON 
    DECLARE @FileID int
    SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    IF @@ROWCOUNT=0
    BEGIN
        INSERT INTO [dbo].[File]([Name])
        VALUES (@Name)
        SELECT @FileID = [FileID] FROM [dbo].[File] WHERE [Name] = @Name
    END

    SELECT * FROM [dbo].[File]
    WHERE [FileID] = @FileID

    COMMIT TRANSACTION xact_File_Create
GO

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

Спасибо

Ответы [ 3 ]

1 голос
/ 21 января 2009

Сначала создайте уникальный индекс в столбце Имя. Затем из своего клиентского кода сначала проверьте, существует ли Имя, выбрав FileID и поместив Имя в предложении where - если это так, используйте FileID. Если нет, вставьте новый.

1 голос
/ 21 января 2009

Если вы интенсивно выполняете поиск в поле «Имя», вы, вероятно, захотите, чтобы оно было проиндексировано (как уникальное и даже кластеризованное, если это поле поиска primary ). Поскольку вы не используете @FileID при первом выборе, я бы просто выбрал count (*) из файла, где Name = @Name, и посмотрю, будет ли он больше нуля (это помешает SQL сохранить любые блокировки таблицы из этап поиска, так как столбцы не выбраны).

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

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

РЕДАКТИРОВАТЬ: индексируя таблицу, но не используя SERIALIZABLE, вы получаете три случая:

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

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

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

0 голосов
/ 21 января 2009

Использование функции Exists может немного привести в порядок вещи.

if (Exists(select * from table_name where column_name = @param)
begin
  //use existing file name
end
else
  //use new file name
...