Виконання некластеризованих індексів на Heaps vs Clustered Index


39

У цій Білій книзі 2007 року порівнюються показники для окремих операцій вибору / вставлення / видалення / оновлення та діапазону вибору діапазону на таблиці, організованій як кластерний індекс, та порівнянні з таблицею, організованою як купа, з некластеризованим індексом у тих самих стовпцях клавіш, що і CI стіл.

Як правило, параметр кластерного індексу є більш ефективним у тестах, оскільки існує лише одна структура для підтримки і тому, що немає необхідності в пошуку закладок.

Один з потенційно цікавих випадків, не охоплених статтею, був би порівнянням некластеризованого індексу на купі та некластеризованого індексу кластерного індексу. У цьому випадку я б очікував, що купа може навіть краще працювати, як колись на рівні листів NCI, SQL Server має RID, який слід безпосередньо, а не потребувати переходу кластерного індексу.

Хтось знає про подібне формальне тестування, яке було проведено в цій галузі, і якщо так, то які були результати?

Відповіді:


41

Щоб перевірити ваш запит, я створив 2 таблиці за цією схемою:

  • 7,9 млн записів, що представляють інформацію про баланс.
  • поле ідентичності налічує від 1 до 7,9 мільйона
  • числове поле, що групує записи приблизно в 500 тис. груп.

Перша названа таблиця heapотримала некластеризований індекс на полі group. Друга названа таблиця clustотримала кластерний індекс на послідовному полі, що називається, keyі некластеризованому індексі на поліgroup

Тести проводилися на процесорі I5 M540 з 2 гіперточеними ядрами, 4 Гб оперативної пам’яті та 64-бітним вікном 7.

Microsoft SQL Server 2008 R2 (RTM) - 10.50.1600.1 (X64) 
Apr  2 2010 15:48:46 
Developer Edition (64-bit) on Windows NT 6.1 <X64> (Build 7601: Service Pack 1)  

Оновлення 9 березня 2011 року : Я зробив другий більш обширний орієнтир, запустивши наступний .net-код і ввівши тривалість, CPU, читання, записи та RowCounts у програмі Sql Server Profiler. (Використовуваний CommandText буде згаданий в результатах.)

ПРИМІТКА. Процесор і тривалість виражаються в мілісекундах

  • 1000 запитів
  • нульові запити процесора видаляються з результатів
  • 0 результатів усуваються з результатів
int[] idList = new int[] { 6816588, 7086702, 6498815 ... }; // 1000 values here.
using (var conn = new SqlConnection(@"Data Source=myserver;Initial Catalog=mydb;Integrated Security=SSPI;"))
            {
                conn.Open();
                using (var cmd = new SqlCommand())
                {
                    cmd.Connection = conn;
                    cmd.CommandType = CommandType.Text;
                    cmd.CommandText = "select * from heap where common_key between @id and @id+1000"; 
                    cmd.Parameters.Add("@id", SqlDbType.Int);
                    cmd.Prepare();
                    foreach (int id in idList)
                    {
                        cmd.Parameters[0].Value = id;

                        using (var reader = cmd.ExecuteReader())
                        {
                            int count = 0;
                            while (reader.Read())
                            {
                                count++;
                            }
                            Console.WriteLine(String.Format("key: {0} => {1} rows", id, count));
                        }
                    }
                }
            }

Кінець поновлення 9 березня 2011 року .

Виберіть продуктивність

Щоб перевірити номери performanc, я здійснив наступні запити один раз на таблиці купівлі та один раз у таблиці стисів:

select * from heap/clust where group between 5678910 and 5679410
select * from heap/clust where group between 6234567 and 6234967
select * from heap/clust where group between 6455429 and 6455729
select * from heap/clust where group between 6655429 and 6655729
select * from heap/clust where group between 6955429 and 6955729
select * from heap/clust where group between 7195542 and 7155729

Результати цього еталону є для heap:

rows  reads CPU   Elapsed 
----- ----- ----- --------
1503  1510  31ms  309ms
401   405   15ms  283ms
2700  2709  0ms   472ms
0     3     0ms   30ms
2953  2962  32ms  257ms
0     0     0ms   0ms

Оновлення 9 березня 2011 року : cmd.CommandText = "select * from heap where group between @id and @id+1000";

  • 721 рядки мають> 0 процесора і стосуються більше 0 рядків
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1001      69788    6368         -         
Cpu            15        374      37   0.00754
Reads        1069      91459    7682   1.20155
Writes          0          0       0   0.00000
Duration   0.3716   282.4850 10.3672   0.00180

Кінець поновлення 9 березня 2011 року .


для таблиці clustрезультати:

rows  reads CPU   Elapsed 
----- ----- ----- --------
1503  4827  31ms  327ms
401   1241  0ms   242ms
2700  8372  0ms   410ms
0     3     0ms   0ms
2953  9060  47ms  213ms
0     0     0ms   0ms

Оновлення 9 березня 2011 року : cmd.CommandText = "select * from clust where group between @id and @id+1000";

  • 721 рядки мають> 0 процесора і стосуються більше 0 рядків
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1001      69788    6056         -
Cpu            15        468      38   0.00782
Reads        3194     227018   20457   3.37618
Writes          0          0       0       0.0
Duration   0.3949   159.6223 11.5699   0.00214

Кінець поновлення 9 березня 2011 року .


ВИБІРТЕ З ПРИЄДНАЙТЕ

cmd.CommandText = "select * from heap/clust h join keys k on h.group = k.group where h.group between @id and @id+1000";


Результати цього еталону є для heap:

873 Рядки мають> 0 ЦП і стосуються понад 0 рядків

Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1009       4170    1683         -
Cpu            15         47      18   0.01175
Reads        2145       5518    2867   1.79246
Writes          0          0       0   0.00000
Duration   0.8215   131.9583  1.9095   0.00123

Результати цього еталону є для clust:

865 рядків мають> 0 процесора і стосуються понад 0 рядків

Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1000       4143    1685         -
Cpu            15         47      18   0.01193
Reads        5320      18690    8237   4.97813
Writes          0          0       0   0.00000
Duration   0.9699    20.3217  1.7934   0.00109

ОНОВЛЕННЯ продуктивності

Друга партія запитів - це оператори оновлення:

update heap/clust set amount = amount + 0 where group between 5678910 and 5679410
update heap/clust set amount = amount + 0 where group between 6234567 and 6234967
update heap/clust set amount = amount + 0 where group between 6455429 and 6455729
update heap/clust set amount = amount + 0 where group between 6655429 and 6655729
update heap/clust set amount = amount + 0 where group between 6955429 and 6955729
update heap/clust set amount = amount + 0 where group between 7195542 and 7155729

результати цього показника для heap:

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
1503  3013  31ms  175ms
401   806   0ms   22ms
2700  5409  47ms  100ms
0     3     0ms   0ms
2953  5915  31ms  88ms
0     0     0ms   0ms

Оновлення 9 березня 2011 року : cmd.CommandText = "update heap set amount = amount + @id where group between @id and @id+1000";

  • 811 рядків мають> 0 процесора і стосуються понад 0 рядків
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1001      69788    5598       811         
Cpu            15        873      56   0.01199
Reads        2080     167593   11809   2.11217
Writes          0       1687     121   0.02170
Duration   0.6705   514.5347 17.2041   0.00344

Кінець поновлення 9 березня 2011 року .


результати цього показника для clust:

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
1503  9126  16ms  35ms
401   2444  0ms   4ms
2700  16385 31ms  54ms
0     3     0ms   0ms 
2953  17919 31ms  35ms
0     0     0ms   0ms

Оновлення 9 березня 2011 року : cmd.CommandText = "update clust set amount = amount + @id where group between @id and @id+1000";

  • 853 Рядки мають> 0 ЦП і стосуються понад 0 рядків
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1001      69788    5420         -
Cpu            15        594      50   0.01073
Reads        6226     432237   33597   6.20450
Writes          0       1730     110   0.01971
Duration   0.9134   193.7685  8.2919   0.00155

Кінець поновлення 9 березня 2011 року .


Зняти орієнтири

третя група запитів, якими я керувала, - це заяви видалення

delete heap/clust where group between 5678910 and 5679410
delete heap/clust where group between 6234567 and 6234967
delete heap/clust where group between 6455429 and 6455729
delete heap/clust where group between 6655429 and 6655729
delete heap/clust where group between 6955429 and 6955729
delete heap/clust where group between 7195542 and 7155729

Результат цього показника для heap:

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
1503  10630 62ms  179ms
401   2838  0ms   26ms
2700  19077 47ms  87ms
0     4     0ms   0ms
2953  20865 62ms  196ms
0     4     0ms   9ms

Оновлення 9 березня 2011 року : cmd.CommandText = "delete heap where group between @id and @id+1000";

  • 724 рядки мають> 0 процесора і стосуються більше 0 рядків
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts     192      69788    4781         -
Cpu            15        499      45   0.01247
Reads         841     307958   20987   4.37880
Writes          2       1819     127   0.02648
Duration   0.3775  1534.3383 17.2412   0.00349

Кінець поновлення 9 березня 2011 року .


результат цього показника для clust:

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
1503  9228  16ms  55ms
401   3681  0ms   50ms
2700  24644 46ms  79ms
0     3     0ms   0ms
2953  26955 47ms  92ms
0     3     0ms   0ms

Оновлення 9 березня 2011 року :

cmd.CommandText = "delete clust where group between @id and @id+1000";

  • 751 рядок має> 0 процесора і стосується більше 0 рядків
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts     144      69788    4648         -
Cpu            15        764      56   0.01538
Reads         989     458467   30207   6.48490
Writes          2       1830     127   0.02694
Duration   0.2938  2512.1968 24.3714   0.00555

Кінець поновлення 9 березня 2011 року .


INSERT орієнтири

Остання частина еталону - це виконання вставних операторів.

вставити в значення heap / clust (...) (...), (...), (...), (...), (...), (...)


Результат цього показника для heap:

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
6     38    0ms   31ms

Оновлення 9 березня 2011 року :

string str = @"insert into heap (group, currency, year, period, domain_id, mtdAmount, mtdAmount, ytdAmount, amount, ytd_restated, restated, auditDate, auditUser)
                    values";

                    for (int x = 0; x < 999; x++)
                    {
                        str += string.Format(@"(@id + {0}, 'EUR', 2012, 2, 0, 100, 100, 1000 + @id,1000, 1000,1000, current_timestamp, 'test'),  ", x);
                    }
                    str += string.Format(@"(@id, 'CAD', 2012, 2, 0, 100, 100, 1000 + @id,1000, 1000,1000, current_timestamp, 'test') ", 1000);

                    cmd.CommandText = str;
  • 912 заяв мають> 0 ЦП
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1000       1000    1000         -
Cpu            15       2138      25   0.02500
Reads        5212       7069    6328   6.32837
Writes         16         34      22   0.02222
Duration   1.6336   293.2132  4.4009   0.00440

Кінець поновлення 9 березня 2011 року .


Результат цього показника для clust:

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
6     50    0ms   18ms

Оновлення 9 березня 2011 року :

string str = @"insert into clust (group, currency, year, period, domain_id, mtdAmount, mtdAmount, ytdAmount, amount, ytd_restated, restated, auditDate, auditUser)
                    values";

                    for (int x = 0; x < 999; x++)
                    {
                        str += string.Format(@"(@id + {0}, 'EUR', 2012, 2, 0, 100, 100, 1000 + @id,1000, 1000,1000, current_timestamp, 'test'),  ", x);
                    }
                    str += string.Format(@"(@id, 'CAD', 2012, 2, 0, 100, 100, 1000 + @id,1000, 1000,1000, current_timestamp, 'test') ", 1000);

                    cmd.CommandText = str;
  • 946 заяв мають> 0 ЦП
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1000       1000    1000         -      
Cpu            15       2403      21   0.02157
Reads        6810       8997    8412   8.41223
Writes         16         25      19   0.01942
Duration   1.5375   268.2571  6.1463   0.00614

Кінець поновлення 9 березня 2011 року .


Висновки

Хоча при зверненні до таблиці з кластеризованим & некластеризованим індексом (при використанні некластеризованого індексу) відбуваються більш логічні зчитування, результати:

  • Вираження SELECT порівнянні
  • Операції UPDATE швидше з кластерним індексом на місці
  • DELETE оператори швидше, коли кластерний індекс на місці
  • Висловлювання INSERT швидше з кластерним індексом на місці

Звичайно, мій орієнтир був дуже обмежений у конкретному виді таблиці та з дуже обмеженим набором запитів, але я думаю, що виходячи з цієї інформації, ми вже можемо почати говорити, що практично завжди краще створити кластерний індекс на вашій таблиці.

Оновлення 9 березня 2011 року :

Як ми бачимо з доданих результатів, висновки щодо обмежених тестів були не правильними в кожному випадку.

Зважена тривалість

Результати тепер показують, що єдиними операторами, які користуються кластерним індексом, є заяви оновлення. Інші твердження на 30% повільніше на столі з кластерним індексом.

Деякі додаткові діаграми, в яких я побудував зважену тривалість на запит для heap vs clust. Зважена тривалість купи та кластеризовані для Select

Зважена тривалість купи та кластеризовані для Join

Зважена тривалість купи та кластеризовані для оновлення

Зважена тривалість купи та кластеризовані для видалення

Як ви бачите профіль продуктивності для операторів вставки досить цікавий. Шипи викликані кількома точками даних, які потребують набагато більше часу. Зважена тривалість купи та кластеризовані для Insert

Кінець поновлення 9 березня 2011 року .


@Martin Я спробую запустити це на сервері з кількома таблицями з 500 мільйонами записів, коли знайду деякий час на наступному тижні.
Філіп Де Вос

Я сумніваюся в правдивості цього тесту. Деякі частини потребують серйозної уваги, наприклад, продуктивність INSERT, яка стверджує, що кластерний індекс швидший - у CLUST-версії було більше зчитування, але минулий час менше. Я особисто ігнорував би минулий час, що знаходиться в межах 10 секунд мілісекунд (мінливість часу) - це означає менше, ніж кількість прочитаних.

Ознайомтесь із суперечкою "Кластерний індекс" Кімберлі Трипп, де вона пояснює, чому більшість (якщо не всі) операції зі скупченою таблицею проходять швидше, ніж з купою - деякі суперечать вашим результатам ...
marc_s

1
@Martin, @Richard, @marc_s. Я зараз працюю над більш серйозним орієнтиром. Я сподіваюся, що зможу сьогодні додати результати пізніше.
Філіп Де Вос

1
@Filip - Нічого собі! Ви точно заслуговуєте на винагороду за всю нелегку працю, яку ви поставили до цієї відповіді. Хоча, як ви цілком справедливо зазначаєте, це був один орієнтир у конкретному виді таблиці з дуже обмеженим набором запитів, і пробіг безсумнівно відрізнятиметься.
Мартін Сміт

12

Як Кімберлі Тріпп - Королева індексації - досить непогано пояснює у своєму блозі The Clustered Index Debate ... , маючи кластерний клавіш на таблиці бази даних, значно прискорює всі операції - не тільки SELECT.

SELECT, як правило, повільніше на купі порівняно з кластеризованою таблицею, якщо ви вибрали хороший кластерний ключ - щось на зразок an INT IDENTITY. Якщо ви використовуєте дійсно поганий ключ кластеризації, наприклад GUID або складний ключ з великою кількістю компонентів змінної довжини, тоді, але тільки тоді, купа може бути швидшою. Але в цьому випадку вам справді потрібно очистити дизайн бази даних в першу чергу ...

Так що в цілому, я не думаю, що в купі немає сенсу - виберіть хороший, корисний ключ кластеризації, і ви повинні отримати користь у всіх відношеннях.


3
Це невідповідь. Мартін досить міцний на SQL Server; це питання мало на меті отримати перевірені результати в реальному світі перевірених результатів, а не більше теорії.

Зв'язана стаття Кімберлі Трипп передбачає, що всі некластеризовані індекси охоплюють. Якщо це так, то пошуку не було б, і перевага купи в пошукових запитах буде заперечена. Але це не світ, у якому живе більшість із нас. У наших випадках намагання спроектувати всі або більшість наших некластеризованих індексів для покриття створює власні проблеми.

@ dbaguy52: чому ви вважаєте, що Кім Трипп передбачає, що всі індекси NC охоплюють? Я не бачу ніякого уявлення про те , що в своєму блозі пост ..... будь ласка більш детально пояснити , що змушує вас вірити , що це так (або це її припущення)
marc_s

7

Щойно трапилося зіткнутися з цією статтею Джо Чанга, яка стосується цього питання. Свої висновки вставив нижче.

Розглянемо таблицю, для якої індекси мають глибину 4, так що є кореневий рівень, 2 проміжні рівні та рівень листя. Шукання індексу для одного індексного ключа (тобто відсутність пошуку ключів) генерувало б 4 логічних IO (LIO). Тепер подумайте, чи потрібен пошук ключа. Якщо таблиця має також кластерний індекс на глибині 4, кожен пошук ключів генерує 4 LIO. Якщо таблиця була купою, кожен пошук ключа генерує 1 LIO. Насправді пошук ключа до купи коштує приблизно на 20-30% дешевше, ніж пошук ключів до кластерного індексу, а не десь близько до співвідношення LIO 4: 1.


1
Цікаво відзначити, що цитата Джо Чанга визначила перевагу ефективності у 20-30% для груп, виходячи з його припущень, що майже та сама перевага, визначена в оновленні статті 9 березня.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.