Рассматривая планы запроса для INSERT, UPDATE или DELETE, в том числе те, которые демонстрировались в некоторых статьях ранее, можно заметить, что почти все такие планы включают оператора TOP. Например, следующий ниже сценарий с оператором UPDATE создает показательный для демонстрации этого план:

CREATE TABLE T (A INT) 
INSERT T VALUES (0) 
INSERT T VALUES (1) 
INSERT T VALUES (2)

UPDATE T SET A = A + 1
Rows  Executes
3      1      UPDATE [T] set [A] = [A]+@1
3      1        |--Table Update(OBJECT:([T]), SET:([T].[A] = [Expr1004]))
0      0             |--Compute Scalar(DEFINE:([Expr1004]=[T].[A]+[@1]))
3      1                  |--Top(ROWCOUNT est 0)
3      1                       |--Table Scan(OBJECT:([T]))

Что TOP делает прямо после просмотра таблицы?

Это ROWCOUNT TOP. Он используется для реализации функциональности SET ROWCOUNT. "est 0" указывает на то что, когда запрос был скомпилирован, SET ROWCOUNT был равен нулю («est» — это сокращение от «estimate», хотя это значение во время компиляции не влияет на оптимизацию или выполнение запроса). Отметим, что значение 0 означает выборку или обновление всех строк. Поскольку во время выполнения SET ROWCOUNT также был равен 0, возвращаемый STATISTICS PROFILE результат, показывает, что все 3 строки были обновлены.

Теперь попробуйте следующее:

SET ROWCOUNT 1
UPDATE T SET A = A + 1
Rows  Executes
1      1      UPDATE [T] set [A] = [A]+@1
1      1        |--Table Update(OBJECT:([T]), SET:([T].[A] = [Expr1004]))
0      0             |--Compute Scalar(DEFINE:([Expr1004]=[T].[A]+[@1]))
1      1                  |--Top(ROWCOUNT est 0)
1      1                       |--Table Scan(OBJECT:([T]))

Хотя мы получаем тот же план (включая ROWCOUNT TOP с той же «estimate»), в этот раз SET ROWCOUNT был равен 1, поэтому при просмотре таблицы TOP вернул только одну строку, и потому была обновлена только одна строка.

Если сделать рекомпиляцию, то значение «estimate» изменяется:

SET ROWCOUNT 1
UPDATE T SET A = A + 1 OPTION (RECOMPILE)
Rows  Executes
1      1      UPDATE T SET A = A + 1 OPTION (RECOMPILE)
1      1        |--Table Update(OBJECT:([T]), SET:([T].[A] = [Expr1004]))
0      0             |--Compute Scalar(DEFINE:([Expr1004]=[T].[A]+(1)))
1      1                  |--Top(ROWCOUNT est 1)
1      1                       |--Table Scan(OBJECT:([T]))

Почему SQL Server не добавляет ROWCOUNT TOP к операторам выборки?

Например, следующий план запроса не содержит TOP, но возвращает только одну строку:

SET ROWCOUNT 1
SELECT * FROM T
Rows  Executes
1      1      SELECT * FROM T
1      1        |--Table Scan(OBJECT:([T]))

SQL Server реализует SET ROWCOUNT для операторов SELECT простым подсчётом, выбирая указанное количество строк из корня плана. Хотя это может работать и для тривиального плана с UPDATE, наподобие того, который был показан выше, это не будет работать для более сложных планов с UPDATE. Например, если мы добавим в нашу таблицу уникальный индекс, план с UPDATE станет существенно сложнее:

CREATE UNIQUE INDEX TA ON T(A) 
UPDATE T SET A = A + 1
Rows  Executes
2      1      UPDATE [T] set [A] = [A]+@1
2      1        |--Index Update(OBJECT:([T].[TA]), SET:([Bmk10061024] = [Bmk1006],[A1025] = [T].[A]))
2      1             |--Collapse(GROUP BY:([T].[A]))
2      1                  |--Filter(WHERE:(NOT [Expr1021]))
2      1                       |--Sort(ORDER BY:([T].[A] ASC, [Act1023] ASC))
2      1                            |--Split
1      1                                 |--Table Update(OBJECT:([T]), SET:([T].[A] = [Expr1004]))
1      1                                      |--Compute Scalar(DEFINE:([Expr1021]=[Expr1021]))
0      0                                           |--Compute Scalar(DEFINE:([Expr1021]=CASE WHEN [Expr1005] THEN (1) ELSE (0) END))
0      0                                                |--Compute Scalar(DEFINE:([Expr1004]=[T].[A]+(1), [Expr1005]=CASE WHEN [T].[A] = ([T].[A]+(1)) THEN (1) ELSE (0) END))
1      1                                                     |--Top(ROWCOUNT est 1)
1      1                                                          |--Table Scan(OBJECT:([T]))

Мы не будем в этой статье разбирать все детали последнего плана. Оставим это для последующих статей. Однако обратите внимание, что при изменении одной строки, корень этого плана возвращает две строки. Если бы было отсчитана одна строка от корня плана, это не дало бы правильного результата. Поместив ROWCOUNT TOP над просмотром таблицы, оптимизатор может гарантировать, что сервер обновит правильное количество строк независимо от сложности остальной части плана.

Комментарии (0)