Оценщик мощности SQL Server делает различные предположения моделирования, такие как
- Независимость. Распределение данных по разным столбцам является независимым, если не доступна информация о корреляции.
- Однородность: на каждом шаге гистограммы объекта статистики равномерно распределены различные значения, и каждое значение имеет одинаковую частоту.
Источник
В таблице 810 064 строки.
У вас есть запрос
SELECT COUNT(*),
MIN(startdate) AS Firstdate,
MAX(startdate) AS Lastdate
FROM table
WHERE status <> 'A'
AND fk = 4193
1 893 (0,23%) строки соответствуют предикату fk = 4193
, а из этих двух нет части status <> 'A'
, поэтому в целом 1891 совпадают и их необходимо агрегировать.
У вас также есть два индекса, ни один из которых не охватывает весь запрос.
Для вашего быстрого запроса он использует индекс на fk
, чтобы непосредственно найти строки, где fk = 4193
затем необходимо выполнить 1893 поиск ключей , чтобы найти каждую строку в кластерном индексе для проверки status
предикат и извлечение startdate
для агрегации.
При удалении COUNT(*)
из списка SELECT
SQL Server больше не имеет для обработки каждой подходящей строки. В результате он рассматривает другой вариант.
У вас есть индекс на startdate
, чтобы он мог начать его сканирование с самого начала, выполняя поиск по ключам обратно к базовой таблице и, как только он находит первую подходящую остановку строки, как он обнаружил MIN(startdate)
, аналогично MAX
можно найти при другом сканировании, начиная с другого конца индекса и возвращаясь назад.
По оценкам SQL Server, при каждом из этих сканирований будет обработано 590 строк, прежде чем они попадут в одну, соответствующую предикату. Общее количество поисков составляет 1180 против 1893, поэтому он выбирает этот план.
590 цифра просто table_size / estimated_number_of_rows_that_match
. то есть оценщик мощности предполагает, что совпадающие строки будут равномерно распределены по всей таблице.
К сожалению, 1891 строка, которая соответствует предикату, не случайным образом распределена относительно startdate
. Фактически все они сжаты в один 8,205-строчный сегмент к концу индекса, что означает, что сканирование, чтобы добраться до MIN(startdate)
, завершается выполнением 801 859 поисков ключа, прежде чем он может остановиться.
Это можно воспроизвести ниже.
CREATE TABLE T
(
id int identity(1,1) primary key,
startdate datetime,
fk int,
[status] char(1),
Filler char(2000)
)
CREATE NONCLUSTERED INDEX ix ON T(startdate)
INSERT INTO T
SELECT TOP 810064 Getdate() - 1,
4192,
'B',
''
FROM sys.all_columns c1,
sys.all_columns c2
UPDATE T
SET fk = 4193, startdate = GETDATE()
WHERE id BETWEEN 801859 and 803748 or id = 810064
UPDATE T
SET startdate = GETDATE() + 1
WHERE id > 810064
/*Both queries give the same plan.
UPDATE STATISTICS T WITH FULLSCAN
makes no difference*/
SELECT MIN(startdate) AS Firstdate,
MAX(startdate) AS Lastdate
FROM T
WHERE status <> 'A' AND fk = 4192
SELECT MIN(startdate) AS Firstdate,
MAX(startdate) AS Lastdate
FROM T
WHERE status <> 'A' AND fk = 4193
Вы можете использовать подсказки запросов, чтобы план использовал индекс на fk
вместо startdate
, или добавить предложенный отсутствующий индекс, выделенный в плане выполнения на (fk,status) INCLUDE (startdate)
, чтобы избежать этой проблемы.