He tenido un debate en curso con varios desarrolladores en mi oficina sobre el costo de un índice y si la unicidad es beneficiosa o costosa (probablemente ambas). El quid de la cuestión son nuestros recursos competidores.
Antecedentes
Anteriormente leí una discusión que decía que un Unique
índice no tiene costo adicional para mantener, ya que una Insert
operación verifica implícitamente dónde encaja en el árbol B y, si se encuentra un duplicado en un índice no único, agrega un uniquifier a el final de la clave, pero por lo demás se inserta directamente. En esta secuencia de eventos, un Unique
índice no tiene costo adicional.
Mi compañero de trabajo combate esta afirmación diciendo que Unique
se aplica como una segunda operación después de buscar la nueva posición en el árbol B y, por lo tanto, es más costoso de mantener que un índice no único.
En el peor de los casos, he visto tablas con una columna de identidad (inherentemente única) que es la clave de agrupación de la tabla, pero explícitamente declarada como no única. En el otro lado de lo peor está mi obsesión con la unicidad, y todos los índices se crean como únicos, y cuando no es posible definir una relación explícitamente única con un índice, agrego el PK de la tabla al final del índice para asegurar que La unicidad está garantizada.
Con frecuencia participo en revisiones de código para el equipo de desarrollo, y necesito poder dar pautas generales para que sigan. Sí, cada índice debe evaluarse, pero cuando tiene cinco servidores con miles de tablas cada uno y hasta veinte índices en una tabla, debe poder aplicar algunas reglas simples para garantizar un cierto nivel de calidad.
Pregunta
¿La unicidad tiene un costo adicional en el back-end de una Insert
comparación con el costo de mantener un índice no único? En segundo lugar, ¿qué tiene de malo agregar la clave primaria de una tabla al final de un índice para garantizar la unicidad?
Definición de tabla de ejemplo
create table #test_index
(
id int not null identity(1, 1),
dt datetime not null default(current_timestamp),
val varchar(100) not null,
is_deleted bit not null default(0),
primary key nonclustered(id desc),
unique clustered(dt desc, id desc)
);
create index
[nonunique_nonclustered_example]
on #test_index
(is_deleted)
include
(val);
create unique index
[unique_nonclustered_example]
on #test_index
(is_deleted, dt desc, id desc)
include
(val);
Ejemplo
Un ejemplo de por qué agregaría la Unique
clave al final de un índice está en una de nuestras tablas de hechos. Hay una Primary Key
que es una Identity
columna. Sin embargo, Clustered Index
es la columna del esquema de partición, seguida de tres dimensiones de clave externa sin unicidad. Seleccionar el rendimiento en esta tabla es abismal, y frecuencia me buscan mejores tiempos con el Primary Key
con una búsqueda de claves en lugar de aprovechar el Clustered Index
. Otras tablas que siguen un diseño similar, pero que se han Primary Key
agregado al final tienen un rendimiento considerablemente mejor.
-- date_int is equivalent to convert(int, convert(varchar, current_timestamp, 112))
if not exists(select * from sys.partition_functions where [name] = N'pf_date_int')
create partition function
pf_date_int (int)
as range right for values
(19000101, 20180101, 20180401, 20180701, 20181001, 20190101, 20190401, 20190701);
go
if not exists(select * from sys.partition_schemes where [name] = N'ps_date_int')
create partition scheme
ps_date_int
as partition
pf_date_int all
to
([PRIMARY]);
go
if not exists(select * from sys.objects where [object_id] = OBJECT_ID(N'dbo.bad_fact_table'))
create table dbo.bad_fact_table
(
id int not null, -- Identity implemented elsewhere, and CDC populates
date_int int not null,
dt date not null,
group_id int not null,
group_entity_id int not null, -- member of group
fk_id int not null,
-- tons of other columns
primary key nonclustered(id, date_int),
index [ci_bad_fact_table] clustered (date_int, group_id, group_entity_id, fk_id)
)
on ps_date_int(date_int);
go
if not exists(select * from sys.objects where [object_id] = OBJECT_ID(N'dbo.better_fact_table'))
create table dbo.better_fact_table
(
id int not null, -- Identity implemented elsewhere, and CDC populates
date_int int not null,
dt date not null,
group_id int not null,
group_entity_id int not null, -- member of group
-- tons of other columns
primary key nonclustered(id, date_int),
index [ci_better_fact_table] clustered(date_int, group_id, group_entity_id, id)
)
on ps_date_int(date_int);
go
Case
y cómoIf
se limitan a 10 niveles, tiene sentido que también haya un límite para resolver entidades no únicas. Según su declaración, parece que solo se aplica a casos en los que la clave de agrupación no es única. ¿Es esto un problema paraNonclustered Index
o si la clave de agrupación esUnique
entonces no hay un problema para losNonclustered
índices?