Endeks Benzersizliği Yükü


14

Ofisimdeki çeşitli geliştiricilerle bir endeksin maliyeti hakkında ve benzersizliğin yararlı mı yoksa maliyetli mi (muhtemelen her ikisi de) olup olmadığı konusunda sürekli bir tartışma yaşıyorum. Sorunun en önemli noktası rakip kaynaklarımızdır.

Arka fon

Daha önce Uniquebir Insertişlemin B ağacına sığdığı yeri dolaylı olarak kontrol ettiğinden ve benzersiz olmayan bir dizinde bir yinelenen bulunursa, anahtarın sonuna, ancak doğrudan doğruya takılır. Bu olay dizisinde, bir Uniquedizinin ek maliyeti yoktur.

İş arkadaşım, bu açıklamada Unique, B ağacındaki yeni pozisyon arayışından sonra ikinci bir operasyon olarak uygulandığını ve dolayısıyla benzersiz olmayan bir endekse kıyasla daha maliyetli olduğunu söyleyerek mücadele ediyor .

En kötüsü, tablonun kümeleme anahtarı olan, ancak açıkça benzersiz olmayan olarak belirtilen bir kimlik sütunu (doğası gereği benzersiz) olan tablolar gördüm. En kötünün diğer tarafında ise benzersizliğe olan takıntımdır ve tüm dizinler benzersiz olarak oluşturulur ve bir dizinle açıkça benzersiz bir ilişki tanımlamak mümkün olmadığında, sağlamak için tablonun PK'sini dizinin sonuna eklerim teklik garanti edilir.

Sıklıkla dev ekibinin kod incelemelerine katılıyorum ve takip etmeleri için genel yönergeler verebilmeliyim. Evet, her dizin değerlendirilmelidir, ancak her birinde binlerce tablo bulunan beş sunucunuz ve bir tabloda yirmi kadar dizin varsa, belirli bir kalite düzeyi sağlamak için bazı basit kurallar uygulayabilmeniz gerekir.

Soru

InsertBenzersizliğin, benzersiz olmayan bir dizini sürdürme maliyetine kıyasla ek bir maliyeti var mı ? İkincisi, benzersizliği sağlamak için tablonun Birincil Anahtarını bir dizinin sonuna eklemekle ilgili sorun nedir?

Örnek Tablo Tanımı

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);

Misal

Neden Uniquebir dizin sonuna anahtar eklemek istiyorum bir örnek bizim olgu tablolarından biridir. Bir yoktur Primary Keybir olduğunu Identitysütunu. Ancak, Clustered Indexbunun yerine bölümleme şeması sütunu, ardından benzersiz olmayan üç yabancı anahtar boyutu gelir. Bu tablodaki performans abysmal seçin ve sık sık kullanarak Primary Keybir anahtar arama ile kullanarak daha iyi arama süreleri elde Clustered Index. Benzer bir tasarımı takip eden, ancak Primary Keysonuna kadar eklenen diğer tablolar önemli ölçüde daha iyi performansa sahiptir.

-- 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

Yanıtlar:


16

Sıklıkla dev ekibinin kod incelemelerine katılıyorum ve takip etmeleri için genel yönergeler verebilmeliyim.

Şu anda içinde bulunduğum ortamın 2500 veritabanına sahip 250 sunucusu var. 30.000 veritabanına sahip sistemlerde çalıştım . Dizin oluşturma yönergeleri, bir dizine hangi sütunların dahil edileceği konusunda "kurallar" olmamak üzere adlandırma kuralının vb. Etrafında dönmelidir. Her tek dizin, ilgili iş kuralı veya tabloya dokunan kod için doğru dizin olacak şekilde tasarlanmalıdır.

InsertBenzersizliğin, benzersiz olmayan bir dizini sürdürme maliyetine kıyasla ek bir maliyeti var mı ? İkincisi, benzersizliği sağlamak için tablonun Birincil Anahtarını bir dizinin sonuna eklemekle ilgili sorun nedir?

Benzersiz olmayan bir dizinin sonuna birincil anahtar sütununu eklemek, benzersiz olmasını sağlamak için bana bir anti-desen gibi görünüyor. İş kuralları, verilerin benzersiz olmasını gerektiriyorsa, sütuna benzersiz bir kısıtlama ekleyin; otomatik olarak benzersiz bir dizin oluşturur. Performans için bir sütunu dizine ekliyorsanız, dizine neden bir sütun eklersiniz?

Benzersizliği zorlayan herhangi bir ek yük getirmediği varsayımınız doğru olsa bile (ki bu belirli durumlar için değildir ), endeksi gereksiz yere zorlayarak ne çözüyorsunuz?

Dizin tanımının UNIQUEdeğiştiriciyi içermesini sağlamak için birincil anahtarı dizin anahtarınızın sonuna ekleme özel örneğinde, aslında diskteki fiziksel dizin yapısında sıfır fark yaratır. Bu, B-ağacı dizin anahtarlarının yapısının doğasından kaynaklanır, çünkü her zaman benzersiz olmaları gerekir.

As David Browne Bir yorumda:

Kümelenmemiş her dizin benzersiz dizin olarak depolandığından, benzersiz bir dizine ekleme yapmanın ek bir maliyeti yoktur . Aslında sadece ekstra maliyet içinde olacağını başarısız kümelenmiş dizin tuşları endeksi tuşları eklenecek neden olacak benzersiz bir dizin gibi bir aday anahtar ilan etmek.

Aşağıdaki asgari düzeyde eksiksiz ve doğrulanabilir örneği alın :

USE tempdb;

DROP TABLE IF EXISTS dbo.IndexTest;
CREATE TABLE dbo.IndexTest
(
    id int NOT NULL
        CONSTRAINT IndexTest_pk
        PRIMARY KEY
        CLUSTERED
        IDENTITY(1,1)
    , rowDate datetime NOT NULL
);

İkinci dizin anahtar tanımının kuyruk ucuna birincil anahtar eklemek dışında aynı iki dizin ekleyeceğim:

CREATE INDEX IndexTest_rowDate_ix01
ON dbo.IndexTest(rowDate);

CREATE UNIQUE INDEX IndexTest_rowDate_ix02
ON dbo.IndexTest(rowDate, id);

Ardından, tabloya birkaç satır göndeririz:

INSERT INTO dbo.IndexTest (rowDate)
VALUES (DATEADD(SECOND, 0, GETDATE()))
     , (DATEADD(SECOND, 0, GETDATE()))
     , (DATEADD(SECOND, 0, GETDATE()))
     , (DATEADD(SECOND, 1, GETDATE()))
     , (DATEADD(SECOND, 2, GETDATE()));

Yukarıda görebileceğiniz gibi, üç satır rowDatesütun için aynı değeri ve iki satır benzersiz değerler içerir.

Ardından, belgesiz DBCC PAGEkomutunu kullanarak her dizin için fiziksel sayfa yapılarına bakacağız :

DECLARE @dbid int = DB_ID();
DECLARE @fileid int;
DECLARE @pageid int;
DECLARE @indexid int;

SELECT @fileid = ddpa.allocated_page_file_id
    , @pageid = ddpa.allocated_page_page_id
FROM sys.indexes i 
CROSS APPLY sys.dm_db_database_page_allocations(DB_ID(), i.object_id, i.index_id, NULL, 'LIMITED') ddpa
WHERE i.name = N'IndexTest_rowDate_ix01'
    AND ddpa.is_allocated = 1
    AND ddpa.is_iam_page = 0;

PRINT N'*************************************** IndexTest_rowDate_ix01 *****************************************';
DBCC TRACEON(3604);
DBCC PAGE (@dbid, @fileid, @pageid, 1);
DBCC TRACEON(3604);
PRINT N'*************************************** IndexTest_rowDate_ix01 *****************************************';

SELECT @fileid = ddpa.allocated_page_file_id
    , @pageid = ddpa.allocated_page_page_id
FROM sys.indexes i 
CROSS APPLY sys.dm_db_database_page_allocations(DB_ID(), i.object_id, i.index_id, NULL, 'LIMITED') ddpa
WHERE i.name = N'IndexTest_rowDate_ix02'
    AND ddpa.is_allocated = 1
    AND ddpa.is_iam_page = 0;

PRINT N'*************************************** IndexTest_rowDate_ix02 *****************************************';
DBCC TRACEON(3604);
DBCC PAGE (@dbid, @fileid, @pageid, 1);
DBCC TRACEON(3604);
PRINT N'*************************************** IndexTest_rowDate_ix02 *****************************************';

Ben Beyond Compare kullanarak çıktı baktım ve ayırma sayfa kimlikleri, vb etrafında belirgin farklılıklar dışında, iki dizin yapıları aynıdır.

resim açıklamasını buraya girin

Yukarıdakileri, her dizine birincil anahtarı dahil etmenin ve benzersiz olarak tanımlamanın A Good Thing ™ olduğu anlamına gelebilir, çünkü yine de kapakların altında olan şey budur. Bu varsayımı yapmam ve sadece endeksteki doğal verilerin zaten benzersiz olması durumunda bir endeksi benzersiz olarak tanımlamanızı öneririm.

Interwebz'de bu konuyla ilgili birkaç mükemmel kaynak bulunmaktadır:

Bilginize, bir salt varlığı identitysütununda yok değil tekliği garanti. Bu sütunda depolanan değerlerin benzersiz olmasını sağlamak için sütunu birincil anahtar olarak veya benzersiz bir kısıtlamayla tanımlamanız gerekir . SET IDENTITY_INSERT schema.table ON;İfadende olarak tanımlanan bir sütuna benzersiz olmayan değerlere eklemek sağlayacak identity.


5

Max'in mükemmel cevabına bir eklenti .

Benzersiz olmayan kümelenmiş bir dizin oluşturma söz konusu olduğunda, SQL Server Uniquifierzaten arka planda bir adlı bir şey oluşturur .

Bu Uniquifiersizin platformu CRUD işlemleri bir yeri vardır, bu yana, gelecekte olası sorunlara neden olabilir Uniquifierbununyalnızca 4 bayt (temel 32bit tamsayı) 'dir. Yani, sisteminizde çok fazla CRUD işlemi varsa, mevcut tüm benzersiz sayıları kullanmanız mümkündür ve bir anda bir hata alırsınız ve tablolarınıza artık veri eklemenize izin vermez (çünkü artık yeni eklenen satırlarınıza atanacak benzersiz değerlere sahip değilsiniz).

Bu durumda, bu hatayı alırsınız:

The maximum system-generated unique value for a duplicate group 
was exceeded for index with partition ID (someID). 

Dropping and re-creating the index may resolve this;
otherwise, use another clustering key.

uniquifierTek bir benzersiz olmayan anahtar kümesi için 2,147,483,647 satırdan fazla tüketildiğinde Hata 666 (yukarıdaki hata) oluşur .

Bu nedenle, tek bir anahtar değeri için ~ 2 milyar satıra sahip olmanız veya bu hatayı görmek için ~ 2 milyar kez tek bir anahtar değerini değiştirmeniz gerekir. Bu nedenle, bu sınırlamaya girmeniz pek olası değildir.


Gizli uniquifier'ın anahtar alanından kaçabileceği hakkında hiçbir fikrim yoktu, ama her durumda bazı şeylerin sınırlı olduğunu düşünüyorum. Yapıların nasıl Caseve nasıl If10 seviye ile sınırlı olduğu gibi, benzersiz olmayan varlıkların çözülmesinin de bir sınırı olduğu mantıklıdır. İfadenize göre bu, yalnızca kümeleme anahtarının benzersiz olmadığı durumlar için geçerliymiş gibi görünür. Bu bir sorun mu, Nonclustered Indexyoksa kümeleme anahtarı ise dizinler Uniqueiçin sorun olmaz Nonclusteredmı?
Solonotix

Benzersiz bir dizin (bildiğim kadarıyla) sütun türünün boyutu ile sınırlıdır (eğer bir BIGINT tipi ise, çalışmak için 8 baytınız vardır). Ayrıca, microsoft'un resmi belgelerine göre, kümelenmiş bir dizin için izin verilen en fazla 900 bayt ve kümelenmemiş olanlar için 1700 bayt vardır (çünkü birden fazla kümelenmemiş dizin ve tablo başına yalnızca 1 kümelenmiş dizin olabilir). docs.microsoft.com/tr-tr/sql/sql-server/…
Chessbrain

1
@Solonotix - kümelenmiş dizindeki benzersizleştirici, kümelenmemiş dizinlerde kullanılır. Örneğimdeki kodu birincil anahtar olmadan çalıştırırsanız (bunun yerine kümelenmiş bir dizin oluşturun), çıktının hem benzersiz olmayan hem de benzersiz dizinler için aynı olduğunu görebilirsiniz.
Max Vernon

-2

Bir endeksin benzersiz olup olmayacağı ve bu yaklaşımda daha fazla yük olup olmadığı konusuna değinmeyeceğim. Ama genel tasarımınızda birkaç şey beni rahatsız etti

  1. dt datetime null varsayılan değil (current_timestamp). Tarih / saat daha eski bir form veya bu şekildedir ve datetime2 () ve sysdatetime () yöntemlerini kullanarak en az yer tasarrufu sağlayabilirsiniz.
  2. #test_index (is_deleted) dizininde [nonunique_nonclustered_example] dizinini oluştur (val). Bu beni rahatsız ediyor. Verilere nasıl ulaşılacağına bir göz atın (daha fazlası olduğundan eminim WHERE is_deleted = 0) ve filtrelenmiş bir dizin kullanmaya bakın. Hatta 2 filtrelenmiş endeksleri, için birini kullanarak ele alacak where is_deleted = 0ve diğerwhere is_deleted = 1

Temel olarak bu, gerçek bir problem / çözüm yerine bir hipotezi test etmek için tasarlanmış bir kodlama egzersizi gibi görünüyor, ancak bu iki model kesinlikle kod incelemelerinde aradığım bir şey.


Datetime yerine datetime2 kullanarak en fazla tasarruf edeceğiniz değer 1 bayttır ve bu nedenle hassasiyetiniz 3'ten düşükse, her zaman geçerli bir çözüm olmayan kesirli saniyelerde kesinlik kaybı anlamına gelir. Sağlanan örnek dizine gelince, tasarım soruma odaklanmak için basit tutuldu. Bir Nonclusteredindeks içten kilit aramaları için bir veri satırının sonuna eklenen küme anahtarı olacaktır. Bu nedenle, iki dizin fiziksel olarak aynıdır, bu da benim sorum oldu.
Solonotix

Ölçekte bir veya iki bayt tasarruf etmek için hızlı bir şekilde çalışıyoruz. Ve kesin olmayan tarih saatini kullandığınızdan, hassasiyeti azaltabileceğimizi varsaymıştım. Dizinler için, yine, bit sütunları dizinler üzerinde kurşun sütunlar olarak kötü bir seçim olarak tedavi bir desen olduğunu belirteceğim. Her şeyde olduğu gibi, kilometreniz değişebilir. Ne yazık ki yaklaşık bir modelin dezavantajları.
Toby

-4

Alternatif, daha küçük bir dizin oluşturmak için sadece PK kullanmanız gibi görünüyor. Bu nedenle, üzerindeki performans daha hızlıdır.

Bunu büyük veri tabloları olan şirketlerde görüyorsunuz (örneğin: ana veri tabloları). Birisi, üzerinde çeşitli raporlama gruplarının ihtiyaçlarını karşılamasını bekleyen büyük bir kümelenmiş dizin olmaya karar verir.

Ancak, bir grubun bu endeksin sadece birkaç kısmına ihtiyacı olabilirken, başka bir grubun başka kısımlara ihtiyacı olabilir.

Bu arada, birden çok, daha küçük, hedeflenmiş endeks oluşturmak için onu yıkmak, genellikle sorunu çözer.

Ve yaptığınız şey bu gibi görünüyor. Kötü performansa sahip bu büyük kümelenmiş dizine sahipsiniz, daha sonra (sürpriz yok) daha iyi performansa sahip daha az sütun içeren başka bir dizin oluşturmak için PK kullanıyorsunuz.

Yani, bir analiz yapın ve tek kümelenmiş dizini alıp belirli işlerin ihtiyaç duyduğu daha küçük, hedeflenmiş dizinlere ayırabileceğinizi anlayın.

Performansı daha sonra "tek bir dizinden birden çok dizine" bakış açısından analiz etmeniz gerekir, çünkü dizin oluşturma ve güncelleme konusunda ek yük vardır. Ancak, bunu genel bir perspektiften analiz etmelisiniz.

EG: Bir büyük kümelenmiş dizin için daha az kaynak yoğun olabilir ve daha küçük hedeflenmiş birkaç endekse sahip olmak daha fazla kaynak yoğun olabilir. Ancak, daha sonra arka uçta hedeflenen sorguları çok daha hızlı çalıştırabilir ve orada zamandan (ve paradan) tasarruf ederseniz, buna değebilir.

Bu yüzden, uçtan uca analiz yapmanız gerekir .. sadece kendi dünyanızı nasıl etkilediğine değil, aynı zamanda son kullanıcıları nasıl etkilediğine de bakın.

Sadece PK tanımlayıcısını yanlış kullandığınızı hissediyorum. Ancak, yalnızca 1 dizine (?) İzin veren bir veritabanı sistemi kullanıyor olabilirsiniz, ancak PK'nizde başka bir gizlice gizleyebilirsiniz (bu günlerde her ilişkisel veritabanı sistemi PK'yi otomatik olarak dizine alıyor gibi görünüyor). Bununla birlikte, çoğu modern RDBMS 'çoklu dizin oluşturulmasına izin vermelidir; yapabileceğiniz dizin sayısı için bir sınır olmamalıdır (1 PK sınırının aksine).

Yani, bir alt endeksi gibi davranan bir PK yaparak .. PK'nizi kullanıyorsunuz, bu da tablo daha sonra rolünde genişletilirse gerekli olabilir.

Tablonuzun bir PK'ye ihtiyacı olmadığı anlamına gelmez. SOP DB's 101 “her tablonun bir PK olması gerekir” der. Ancak, veri ambarı veya benzeri bir durumda, bir PK'nin bir masaya sahip olması, ihtiyacınız olmayan ekstra yük olabilir. Veya çift girişli çift girişler eklemediğinizden emin olmak için bir tanrı gönderme olabilir. Bu gerçekten ne yaptığınız ve neden yaptığınızla ilgili.

Ancak, büyük tablolar kesinlikle dizinlere sahip olmaktan faydalanır. Ancak, tek bir büyük kümelenmiş dizin en iyi olacağını varsayalım sadece ... en iyisi olabilir .. ama belirli kullanım senaryolarını hedefleyen birden çok küçük endekse ayırarak bir test env'i test etmenizi tavsiye ederim.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.