Пытаясь отделить приложение от нашей монолитной базы данных, мы попытались изменить столбцы INT IDENTITY различных таблиц на вычисляемый столбец PERSISTED, который использует COALESCE. По сути, нам необходимо, чтобы отделенное приложение могло обновлять базу данных для общих данных, совместно используемых многими приложениями, и в то же время позволяло существующим приложениям создавать данные в этих таблицах без необходимости изменения кода или процедуры.
По сути, мы перешли от определения столбца;
PkId INT IDENTITY(1,1) PRIMARY KEY
к;
PkId AS AS COALESCE(old_id, external_id, new_id) PERSISTED NOT NULL,
old_id INT NULL, -- Values here are from existing records of PkId before table change
external_id INT NULL,
new_id INT IDENTITY(2000000,1) NOT NULL
Во всех случаях PkId также является ПЕРВИЧНЫМ КЛЮЧОМ, и во всех случаях, кроме одного, он КЛАСТЕР. Все таблицы имеют те же внешние ключи и индексы, что и раньше. По сути, новый формат позволяет PkId предоставляться отделенным приложением (как external_id), но также позволяет PkId быть значением столбца IDENTITY, что позволяет существующему коду, который опирается на столбец IDENTITY, используя SCOPE_IDENTITY и @@ IDENTITY. работать как раньше.
Проблема, с которой мы столкнулись, заключается в том, что мы натолкнулись на пару запросов, которые раньше выполнялись в приемлемое время, а теперь полностью исчезли. Сгенерированные планы запросов, используемые этими запросами, не похожи на те, что были раньше.
Учитывая, что новый столбец - PRIMARY KEY, тот же тип данных, что и раньше, и PERSISTED, я ожидал, что запросы и планы запросов будут вести себя так же, как и раньше. Должен ли PkId COMPUTED PERSISTED INT вести себя точно так же, как и явное определение INT с точки зрения того, как SQL Server будет генерировать план выполнения? Есть ли другие вероятные проблемы с этим подходом, которые вы можете увидеть?
Цель этого изменения должна была позволить нам изменить определение таблицы без необходимости изменять существующие процедуры и код. Учитывая эти проблемы, я не чувствую, что мы можем пойти с этим подходом.
источник
Ответы:
ПЕРВЫЙ
Вы , вероятно , не нужны все три колонки:
old_id
,external_id
,new_id
.new_id
Колонна, будучиIDENTITY
, будет иметь новое значение , сгенерированное для каждой строки, даже при вставке вexternal_id
. Но, междуold_id
иexternal_id
, они в значительной степени взаимоисключающие: либоold_id
значение уже есть, либо этот столбец, в текущей концепции, будет простоNULL
при использованииexternal_id
илиnew_id
. Поскольку вы не будете добавлять новый «внешний» идентификатор в строку, которая уже существует (то есть, в которой естьold_id
значение), и не будет никаких новых значенийold_id
, то может быть один столбец, который используется для обеих целей.Таким образом, избавьтесь от
external_id
столбца и переименуйте,old_id
чтобы быть что-то вродеold_or_external_id
или как- то еще. Это не должно требовать каких-либо реальных изменений, но уменьшает некоторые осложнения. Самое большее, вам может понадобиться вызвать столбецexternal_id
, даже если он содержит «старые» значения, если код приложения уже написан для вставкиexternal_id
.Это уменьшает новую структуру, чтобы быть просто:
Теперь вы добавили только 8 байтов в строку вместо 12 байтов (при условии, что вы не используете
SPARSE
опцию или сжатие данных). И вам не нужно было менять код, T-SQL или код приложения.ВТОРОЙ
Продолжая этот путь упрощения, давайте посмотрим на то, что мы оставили:
old_or_external_id
Столбец либо имеет значение уже, или будет дан новое значение из приложения, или оставить какNULL
.new_id
Всегда будет иметь новое значение , сгенерированное, но это значение будет использовано только еслиold_or_external_id
столбецNULL
.Никогда не бывает времени, когда вам понадобятся значения как в, так
old_or_external_id
и вnew_id
. Да, будут времена, когда оба столбца имеют значения из-заnew_id
того, что они являютсяIDENTITY
, но этиnew_id
значения игнорируются. Опять же, эти два поля являются взаимоисключающими. И что теперь?Теперь мы можем понять, зачем нам это было нужно
external_id
. Учитывая , что можно вставить вIDENTITY
колонку с помощьюSET IDENTITY_INSERT {table_name} ON;
, вы могли бы уйти с не делая никаких изменений схемы на всех, и только изменить код приложения , чтобы обернутьINSERT
заявления / операции вSET IDENTITY_INSERT {table_name} ON;
иSET IDENTITY_INSERT {table_name} OFF;
заявлении. Затем вам нужно определить, к какому начальному диапазону будет возвращатьсяIDENTITY
столбец (для вновь сгенерированных значений), так как он должен быть значительно выше значений, которые будет вставлять код приложения, поскольку при вставке более высокого значения следующее автоматически сгенерированное значение будет быть больше, чем текущее значение MAX. Но вы всегда можете вставить значение ниже значения IDENT_CURRENT .Объединяя
old_or_external_id
иnew_id
столбцы также не увеличивают шансы нарваться значением ситуации , перекрывающей между автоматически сгенерированными значениями и приложениями сгенерированными значениями с целью имеющих 2 или даже 3, колонны, чтобы объединить их в значение первичного ключа, и это всегда уникальные ценности.При таком подходе вам просто необходимо:
Оставьте таблицы как:
Это добавляет 0 байтов к каждой строке вместо 8 или даже 12.
SET IDENTITY_INSERT {table_name} ON;
иSET IDENTITY_INSERT {table_name} OFF;
заявления.ВТОРАЯ, часть Б
Вариант подхода, отмеченного непосредственно выше, заключался бы в том, чтобы код приложения вставлял значения, начинающиеся с -1, и понижающиеся оттуда. Это оставляет
IDENTITY
ценности как единственные, идущие вверх . Преимущество здесь в том, что вы не только не усложняете схему, но и не должны беспокоиться о том, чтобы столкнуться с перекрывающимися идентификаторами (если сгенерированные приложением значения попадают в новый автоматически сгенерированный диапазон). Это вариант, только если вы еще не используете отрицательные значения идентификаторов (и люди редко используют отрицательные значения в автоматически сгенерированных столбцах, поэтому в большинстве ситуаций это должно быть вероятно).При таком подходе вам просто необходимо:
Оставьте таблицы как:
Это добавляет 0 байтов к каждой строке вместо 8 или даже 12.
-1
.SET IDENTITY_INSERT {table_name} ON;
иSET IDENTITY_INSERT {table_name} OFF;
заявления.Здесь вам все еще нужно сделать
IDENTITY_INSERT
, но: вы не добавляете новые столбцы, не нуждаетесь в том, чтобы «повторно заполнять» какие-либоIDENTITY
столбцы, и у вас нет будущего риска наложения.ВТОРАЯ, часть 3
Последним вариантом этого подхода может быть замена
IDENTITY
столбцов и использование последовательностей . Причиной такого подхода является наличие возможности вставлять в код приложения значения, которые являются: положительными, выше автоматически сгенерированного диапазона (не ниже) и не нуждаются в этомSET IDENTITY_INSERT ON / OFF
.При таком подходе вам просто необходимо:
Скопируйте
IDENTITY
столбец в новый столбец, который не имеетIDENTITY
свойства, но имеетDEFAULT
ограничение, используя функцию NEXT VALUE FOR :Это добавляет 0 байтов к каждой строке вместо 8 или даже 12.
SET IDENTITY_INSERT {table_name} ON;
иSET IDENTITY_INSERT {table_name} OFF;
заявления.ОДНАКО , из-за требования, чтобы код с одним
SCOPE_IDENTITY()
или@@IDENTITY
все еще функционировал должным образом, переключение на Последовательности в настоящее время не вариант, поскольку кажется, что нет эквивалента этих функций для Последовательностей :-(. Грустно!источник
IDENTITY_INSERT
, но не проверял это. Не уверен, что вариант № 1 решит вашу общую проблему, это было просто наблюдение, чтобы уменьшить ненужную сложность. Тем не менее, если у вас есть несколько потоков, вставляющих новые «внешние» идентификаторы, как вы можете гарантировать их уникальность?IDENTITY_INSERT ON
для одной и той же таблицы в двух сессиях и вставлял в обе без проблем.