логическое чтение для глобальной временной таблицы, но не для временной таблицы на уровне сеанса

11

Рассмотрим следующий простой MCVE:

SET STATISTICS IO, TIME OFF;
USE tempdb;

IF OBJECT_ID(N'tempdb..#t1', N'U') IS NOT NULL DROP TABLE #t1;
CREATE TABLE #t1
(
    r int NOT NULL
);

IF OBJECT_ID(N'tempdb..##t1', N'U') IS NOT NULL DROP TABLE ##t1;
CREATE TABLE ##t1
(
    r int NOT NULL
);

IF OBJECT_ID(N'dbo.s1', N'U') IS NOT NULL DROP TABLE dbo.s1;
CREATE TABLE dbo.s1 
(
    r int NOT NULL
        PRIMARY KEY CLUSTERED
);

INSERT INTO dbo.s1 (r)
SELECT TOP(10000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM sys.syscolumns sc1
    CROSS JOIN sys.syscolumns sc2;
GO

Когда я запускаю следующие вставки, вставка в #t1показывает отсутствие статистики ввода-вывода для временной таблицы. Однако, вставка в ##t1 действительно показывает статистику ввода / вывода для временной таблицы.

SET STATISTICS IO, TIME ON;
GO

INSERT INTO #t1 (r)
SELECT r
FROM dbo.s1;

Вывод статистики:

SQL Server анализирует и компилирует время: 
   Время ЦП = 0 мс, прошедшее время = 1 мс.
Таблица 's1'. Сканирование 1, логическое чтение 19, физическое чтение 0, чтение с опережением 0, логическое чтение с 0, физическое чтение с 0, чтение с опережением 0.

 Время выполнения SQL Server:
   Время ЦП = 16 мс, прошедшее время = 9 мс.

(Затронуто 10000 строк)
INSERT INTO ##t1 (r)
SELECT r
FROM dbo.s1;
SQL Server анализирует и компилирует время: 
   Время ЦП = 0 мс, прошедшее время = 1 мс.
Таблица «## t1». Сканирование 0, логическое чтение 10016, физическое чтение 0, чтение с опережением 0, чтение логического объекта 0, физическое чтение объекта 0, чтение с опережением чтения 0.
Таблица 's1'. Сканирование 1, логическое чтение 19, физическое чтение 0, чтение с опережением 0, логическое чтение с 0, физическое чтение с 0, чтение с опережением 0.

 Время выполнения SQL Server:
   Время процессора = 47 мс, прошедшее время = 45 мс.

(Затронуто 10000 строк)

Почему в ## temp таблице так много операций чтения, когда я только вставляю в нее?

Макс Вернон
источник

Ответы:

11

Минимальное ведение журнала не используется при использовании INSERT INTOглобальных временных таблиц

Вставка миллиона строк в глобальную временную таблицу с помощью INSERT INTO

INSERT INTO ##t1 (r)
SELECT top(1000000) s1.r
FROM dbo.s1
CROSS APPLY  dbo.s1 S2;

При выполнении SELECT * FROM fn_dblog(NULL, NULL)во время выполнения вышеуказанного запроса возвращается ~ 1 млн строк.

введите описание изображения здесь

Одна LOP_INSERT_ROWоперация для каждой строки + другие данные журнала.


Такая же вставка на локальную временную таблицу

INSERT INTO #t1 (r)
SELECT top(1000000) s1.r
FROM dbo.s1
CROSS APPLY  dbo.s1 S2;

Только до 700 строк возвращается SELECT * FROM fn_dblog(NULL, NULL)

введите описание изображения здесь

Минимальная регистрация


Вставка миллиона строк в глобальную временную таблицу с помощью SELECT INTO

SELECT top(1000000) s1.r
INTO ##t2
FROM dbo.s1
CROSS APPLY  dbo.s1 S2;

введите описание изображения здесь

SELECT INTO глобальная временная таблица с 10 тыс. записей

SELECT s1.r
INTO ##t2
FROM dbo.s1;

Время и статистика IO

SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.
Table 's1'. Scan count 1, logical reads 19, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 16 ms,  elapsed time = 10 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

Основываясь на этом посте, мы можем добавить, TABLOCKчтобы инициировать минимальное ведение журнала в таблице кучи.

INSERT INTO ##t1 WITH(TABLOCK) (r)
SELECT   s1.r
FROM dbo.s1

Низкое логическое чтение

Table 's1'. Scan count 1, logical reads 19, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(10000 rows affected)

Часть ответа @PaulWhite о том, как добиться минимального входа во временные таблицы

Нет. Локальные временные таблицы (#temp) являются частными для создаваемого сеанса, поэтому подсказка о блокировке таблицы не требуется. Подсказка о блокировке таблицы потребуется для глобальной временной таблицы (## temp) или обычной таблицы (dbo.temp), созданной в базе данных tempdb, поскольку к ним можно получить доступ из нескольких сеансов.

Создание обычной таблицы для проверки этого:

CREATE TABLE dbo.bla
(
    r int NOT NULL 
);

Заполнение его 1М записями

INSERT INTO bla 
SELECT   top(1000000)s1.r
FROM dbo.s1
CROSS APPLY  dbo.s1 S2;

> 1M логических чтений на этой таблице

Table 's1'. Scan count 17, logical reads 155, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'bla'. Scan count 0, logical reads 1001607, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Ответ Пола Уайта, объясняющий логические чтения, представленные в глобальной таблице временных значений

Как правило, логические чтения сообщаются для целевой таблицы, когда вставка не минимально регистрируется.

Эти логические чтения связаны с поиском места в существующей структуре для добавления новых строк. Вставки с минимальной регистрацией используют механизм массовой загрузки, который распределяет целые новые страницы / экстенты (и поэтому не нужно читать структуру цели таким же образом).


Вывод

Вывод заключается в том, что INSERT INTOон не может использовать минимальное ведение журнала, в результате чего каждая вставленная строка регистрируется отдельно в файле журнала tempdb при использовании в сочетании с глобальной временной таблицей / обычной таблицей. В то время как локальная временная таблица / SELECT INTO/ INSERT INTO ... WITH(TABLOCK)может использовать минимальное ведение журнала.

Рэнди Вертонген
источник