Ефективна ВСТАВКА в таблицю з кластерним індексом


28

У мене є оператор SQL, який вставляє рядки в таблицю з кластерним індексом у стовпці TRACKING_NUMBER.

EG:

INSERT INTO TABL_NAME (TRACKING_NUMBER, COLB, COLC) 
SELECT TRACKING_NUMBER, COL_B, COL_C 
FROM STAGING_TABLE

Моє запитання - чи допомагає це використовувати пункт ORDER BY у операторі SELECT для кластеризованого стовпця індексу, чи будь-який отриманий посилення буде заперечений додатковим сортом, необхідним для пункту ORDER BY?

Відповіді:


18

Оскільки інші відповіді вже вказують, SQL Server може або не може явно гарантувати, що рядки відсортовані в кластерному порядку індексу до insert.

Це залежить від того, чи має кластерний оператор індексу в плані DMLRequestSortнабір властивостей (що, в свою чергу, залежить від передбачуваної кількості вставлених рядків).

Якщо ви виявите , що SQL Server недооцінює це з якихось - небудь причин ви могли б отримати вигоду з додавання явного ORDER BYв SELECTзапиті , щоб звести до мінімуму сторінки розколів і подальше дроблення від INSERTоперації

Приклад:

use tempdb;

GO

CREATE TABLE T(N INT PRIMARY KEY,Filler char(2000))

CREATE TABLE T2(N INT PRIMARY KEY,Filler char(2000))

GO

DECLARE @T TABLE (U UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),N int)

INSERT INTO @T(N)
SELECT number 
FROM master..spt_values
WHERE type = 'P' AND number BETWEEN 0 AND 499

/*Estimated row count wrong as inserting from table variable*/
INSERT INTO T(N)
SELECT T1.N*1000 + T2.N
FROM @T T1, @T T2

/*Same operation using explicit sort*/    
INSERT INTO T2(N)
SELECT T1.N*1000 + T2.N
FROM @T T1, @T T2
ORDER BY T1.N*1000 + T2.N


SELECT avg_fragmentation_in_percent,
       fragment_count,
       page_count,
       avg_page_space_used_in_percent,
       record_count
FROM   sys.dm_db_index_physical_stats(2, OBJECT_ID('T'), NULL, NULL, 'DETAILED')
;  


SELECT avg_fragmentation_in_percent,
       fragment_count,
       page_count,
       avg_page_space_used_in_percent,
       record_count
FROM   sys.dm_db_index_physical_stats(2, OBJECT_ID('T2'), NULL, NULL, 'DETAILED')
;  

Показує, що Tмасово фрагментарно

avg_fragmentation_in_percent fragment_count       page_count           avg_page_space_used_in_percent record_count
---------------------------- -------------------- -------------------- ------------------------------ --------------------
99.3116118225536             92535                92535                67.1668272794663               250000
99.5                         200                  200                  74.2868173956017               92535
0                            1                    1                    32.0978502594514               200

Але для T2фрагментації це мінімально

avg_fragmentation_in_percent fragment_count       page_count           avg_page_space_used_in_percent record_count
---------------------------- -------------------- -------------------- ------------------------------ --------------------
0.376                        262                  62500                99.456387447492                250000
2.1551724137931              232                  232                  43.2438349394613               62500
0                            1                    1                    37.2374598468001               232

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

DECLARE @var INT =2147483647

INSERT INTO Foo
SELECT TOP (@var) *
FROM Bar

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



12

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

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

Редагувати: 2011-10-28 17:00

@ Відповідь Гонсалу показує, що операція сортування завжди відбувається, це не так. Потрібні демо-сценарії!

Оскільки сценарії ставали досить великими, я перемістив їх до Gist . Для зручності експериментів сценарії використовують режим SQLCMD. Тести працюють на 2K5SP3, двоядерний, 8 Гб.

Тести на вставки охоплюють три сценарії:

  1. Постановка даних кластеризована в індексі в тому ж порядку, що і цільове.
  2. Постановка даних кластеризована індексом у зворотному порядку.
  3. Дані про поетапність кластеризовані по Col2, який містить випадковий INT.

Спочатку виконуйте, вставляючи 25 рядів.

1-й пробіг, 25 рядів

Усі три плани виконання однакові, ніде в плані не відбувається, і кластерне сканування індексу "впорядковано = помилково".

Другий запуск, вставляючи 26 рядів.

2-й пробіг, 26 рядів

Цього разу плани відрізняються.

  • Перший показує кластерне сканування індексу як упорядкований = хибний. Жодного сортування не відбулося, оскільки вихідні дані були належним чином відсортовані.
  • У другому кластерне сканування індексів як упорядкований = вірно, назад. Таким чином, у нас немає операції сортування, але необхідність сортування даних визнає оптимізатор і він сканує у зворотному порядку.
  • Третій показує оператора сортування.

Отже, є переломний момент, коли оптимізатор вважає потрібним. Як показує @MartinSmith, схоже, це базується на передбачуваних рядках, які потрібно вставити. На моїй тестовій установці 25 не потрібно сортування, 26 дій (2K5SP3, двоядерний, 8 ГБ)

Сценарій SQLCMD включає змінні, які дозволяють змінювати розмір рядків у таблиці (змінюючи щільність сторінки) та кількість рядків у dbo.MyTable перед додатковими вставками. З мого тестування, жоден вплив на точку перекидання не впливає.

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

Редагувати: 2011-10-28 20:15

Повторні випробування на тій самій установці, але з 2K8R2. Цього разу точка відхилення - 251 ряд. Знову ж таки, зміна щільності сторінки та кількості існуючих рядків не впливає.


8

ORDER BYПункт в SELECTзаяві є зайвим.

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

Створимо тестовий випадок.

CREATE TABLE #Test (
    id INTEGER NOT NULL
);

CREATE UNIQUE CLUSTERED INDEX CL_Test_ID ON #Test (id);

CREATE TABLE #Sequence (
    number INTEGER NOT NULL
);

INSERT INTO #Sequence
SELECT number FROM master..spt_values WHERE name IS NULL;

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

SET STATISTICS PROFILE ON;
GO

Тепер давайте INSERT2К рядків у таблицю без ORDER BYзастереження.

INSERT INTO #Test
SELECT number
  FROM #Sequence

Фактичний план виконання цього запиту наступний.

INSERT INTO #Test  SELECT number    FROM #Sequence
  |--Clustered Index Insert(OBJECT:([tempdb].[dbo].[#Test]), SET:([tempdb].[dbo].[#Test].[id] = [tempdb].[dbo].[#Sequence].[number]))
       |--Top(ROWCOUNT est 0)
            |--Sort(ORDER BY:([tempdb].[dbo].[#Sequence].[number] ASC))
                 |--Table Scan(OBJECT:([tempdb].[dbo].[#Sequence]))

Як бачите, оператор Сортування перед тим, як відбудеться власне ВСТАВКА.

Тепер давайте очистимо таблицю, і INSERT2k рядків у таблицю з ORDER BYпунктом.

TRUNCATE TABLE #Test;
GO

INSERT INTO #Test
SELECT number
  FROM #Sequence
 ORDER BY number

Фактичний план виконання цього запиту наступний.

INSERT INTO #Test  SELECT number    FROM #Sequence   ORDER BY number
  |--Clustered Index Insert(OBJECT:([tempdb].[dbo].[#Test]), SET:([tempdb].[dbo].[#Test].[id] = [tempdb].[dbo].[#Sequence].[number]))
       |--Top(ROWCOUNT est 0)
            |--Sort(ORDER BY:([tempdb].[dbo].[#Sequence].[number] ASC))
                 |--Table Scan(OBJECT:([tempdb].[dbo].[#Sequence]))

Зауважте, що це той самий план виконання, який був використаний для INSERTоператора без ORDER BYпункту.

Тепер Sortоперація не завжди потрібно, оскільки Марк Сміт показав в інший відповідь (якщо число рядків , які будуть вставлені низький), але ORDER BYстановище по - , як і раніше зайвим в цьому випадку, тому що навіть з явним ORDER BY, ні одна Sortоперація не генерується процесором запитів.

Ви можете оптимізувати INSERTвислів у таблицю з кластерним індексом, використовуючи мінімально занесений журнал INSERT, але це не вдається для цього питання.

Оновлено 2011-11-02: Як показав Марк Сміт , INSERTs в таблиці з кластеризованим індексом не завжди може бути потрібна сортування - ORDER BYоднак ця пропозиція також є зайвою у цьому випадку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.