Каков правильный шаблон для уникальных данных в столбцах?

StackOverflow https://stackoverflow.com/questions/463421

  •  19-08-2019
  •  | 
  •  

Вопрос

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

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 используется в качестве ключа для таблицы, а Имя - это полный путь, представляющий файл.

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

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

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

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

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

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

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

Спасибо

Это было полезно?

Решение

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

Вы на правильном пути с СЕРИАЛИЗУЕМЫМ уровнем, так как ваше действие повлияет на успех или неудачу последующих запросов при наличии Имени.Причина, по которой версия без этого набора вызывает дубликаты, заключается в том, что два выбора выполнялись одновременно и обнаружили, что записи нет, поэтому оба выполнили вставки (что создает дубликат).

Тупиковая ситуация с предыдущей версией, скорее всего, связана с отсутствием индекса, из-за чего процесс поиска занимает много времени.Когда вы загружаете сервер в СЕРИАЛИЗУЕМУЮ транзакцию, все остальное должно будет дождаться завершения операции.Индекс следует сделайте операцию быстрой, но только тестирование покажет, достаточно ли она быстрая.Обратите внимание, что вы можете ответить на неудачную транзакцию повторной отправкой:надеемся, что в реальных ситуациях нагрузка будет кратковременной.

Редактировать:Сделав вашу таблицу индексируемой, но не используя SERIALIZABLE, вы получите три варианта:

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

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

Если у вас действительно есть ожидание, что будет обычным делом размещать сообщения с интервалом в миллисекунды друг от друга одного и того же новое назовите, затем используйте СЕРИАЛИЗУЕМУЮ транзакцию в сочетании с индексом.В общем случае это будет медленнее, но быстрее, когда эти записи будут найдены.

Другие советы

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

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

if (Exists(select * from table_name where column_name = @param)
begin
  //use existing file name
end
else
  //use new file name
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top