Ответ 1
Средство оценки мощности 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'
, поэтому общее совпадение составляет 1,891 и должно быть агрегировано.
У вас также есть два индекса, ни один из которых не охватывает весь запрос.
Для вашего быстрого запроса он использует индекс на fk
, чтобы напрямую находить строки, где fk = 4193
, тогда необходимо выполнить 1,893 ключевые поисковые запросы до найдите каждую строку в кластерном индексе, чтобы проверить предикат status
и получить startdate
для агрегации.
При удалении COUNT(*)
из списка SELECT
SQL Server больше не нужно обрабатывать каждую квалификационную строку. В результате он рассматривает другой вариант.
У вас есть индекс на startdate
, чтобы он мог начать сканировать это с самого начала, выполняя ключевые поиски обратно в базовую таблицу и как только обнаруживает, что первая совпадающая строка останавливается, поскольку она нашла MIN(startdate)
, Аналогично MAX
можно найти с другим сканированием, начиная с другого конца индекса и работать в обратном направлении.
SQL Server оценивает, что каждое из этих проверок закончит обработку 590 строк, прежде чем они нажмут на один, который соответствует предикату. Предоставление 1,180 общих запросов против 1,893, поэтому он выбирает этот план.
Рисунок 590 просто table_size / estimated_number_of_rows_that_match
. то есть оценщик мощности предполагает, что соответствующие строки будут равномерно распределены по всей таблице.
К сожалению, 1 891 строк, которые соответствуют предикату, не распределены случайным образом относительно startdate
. На самом деле все они сконденсированы в один сегмент из 8205 строк в конце индекса, означающий, что сканирование, чтобы добраться до 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)
, чтобы избежать этой проблемы.