У меня есть 3 "большие" таблицы, которые объединяются в пару столбцов (обе int
).
- Таблица1 имеет ~ 200 миллионов строк
- Таблица2 имеет ~ 1,5 миллиона строк
- Таблица3 имеет ~ 6 миллионов строк
Каждая таблица имеет кластерный индекс Key1
, Key2
и затем еще один столбец. Key1
имеет низкую мощность и очень искажен. На него всегда ссылаются в WHERE
пункте. Key2
никогда не упоминается в WHERE
пункте. Каждое соединение много ко многим.
Проблема с оценкой мощности. Оценка выхода каждого соединения становится меньше , а не больше . Это приводит к окончательным оценкам малых сотен, когда фактический результат исчисляется миллионами.
Есть ли способ для меня, чтобы убедить СЕ сделать более точные оценки?
SELECT 1
FROM Table1 t1
JOIN Table2 t2
ON t1.Key1 = t2.Key1
AND t1.Key2 = t2.Key2
JOIN Table3 t3
ON t1.Key1 = t3.Key1
AND t1.Key2 = t3.Key2
WHERE t1.Key1 = 1;
Решения, которые я попробовал:
- Создание статистики по нескольким столбцам
Key1
,Key2
- Создание тонны отфильтрованной статистики
Key1
(Это очень помогает, но в итоге я получаю тысячи пользовательских статистик в базе данных).
Маскированный план выполнения (извините за плохую маскировку)
В случае, на который я смотрю, результат имеет 9 миллионов строк. Новый CE оценивает 180 рядов; наследие CE оценивает 6100 строк.
Вот воспроизводимый пример:
DROP TABLE IF EXISTS #Table1, #Table2, #Table3;
CREATE TABLE #Table1 (Key1 INT NOT NULL, Key2 INT NOT NULL, T1Key3 INT NOT NULL, CONSTRAINT pk_t1 PRIMARY KEY CLUSTERED (Key1, Key2, T1Key3));
CREATE TABLE #Table2 (Key1 INT NOT NULL, Key2 INT NOT NULL, T2Key3 INT NOT NULL, CONSTRAINT pk_t2 PRIMARY KEY CLUSTERED (Key1, Key2, T2Key3));
CREATE TABLE #Table3 (Key1 INT NOT NULL, Key2 INT NOT NULL, T3Key3 INT NOT NULL, CONSTRAINT pk_t3 PRIMARY KEY CLUSTERED (Key1, Key2, T3Key3));
-- Table1
WITH Numbers
AS (SELECT TOP (1000000) Number = ROW_NUMBER() OVER(ORDER BY t1.number)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2),
DataSize (Key1, NumberOfRows)
AS (SELECT 1, 2000 UNION
SELECT 2, 10000 UNION
SELECT 3, 25000 UNION
SELECT 4, 50000 UNION
SELECT 5, 200000)
INSERT INTO #Table1
SELECT Key1
, Key2 = ROW_NUMBER() OVER (PARTITION BY Key1, T1Key3 ORDER BY Number)
, T1Key3
FROM DataSize
CROSS APPLY (SELECT TOP(NumberOfRows)
Number
, T1Key3 = Number%(Key1*Key1) + 1
FROM Numbers
ORDER BY Number) size;
-- Table2 (same Key1, Key2 values; smaller number of distinct third Key)
WITH Numbers
AS (SELECT TOP (1000000) Number = ROW_NUMBER() OVER(ORDER BY t1.number)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2)
INSERT INTO #Table2
SELECT DISTINCT
Key1
, Key2
, T2Key3
FROM #Table1
CROSS APPLY (SELECT TOP (Key1*10)
T2Key3 = Number
FROM Numbers
ORDER BY Number) size;
-- Table2 (same Key1, Key2 values; smallest number of distinct third Key)
WITH Numbers
AS (SELECT TOP (1000000) Number = ROW_NUMBER() OVER(ORDER BY t1.number)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2)
INSERT INTO #Table3
SELECT DISTINCT
Key1
, Key2
, T3Key3
FROM #Table1
CROSS APPLY (SELECT TOP (Key1)
T3Key3 = Number
FROM Numbers
ORDER BY Number) size;
DROP TABLE IF EXISTS #a;
SELECT col = 1
INTO #a
FROM #Table1 t1
JOIN #Table2 t2
ON t1.Key1 = t2.Key1
AND t1.Key2 = t2.Key2
WHERE t1.Key1 = 1;
DROP TABLE IF EXISTS #b;
SELECT col = 1
INTO #b
FROM #Table1 t1
JOIN #Table2 t2
ON t1.Key1 = t2.Key1
AND t1.Key2 = t2.Key2
JOIN #Table3 t3
ON t1.Key1 = t3.Key1
AND t1.Key2 = t3.Key2
WHERE t1.Key1 = 1;
источник
make_parallel
Функция Адама используется, чтобы помочь смягчить проблему. Я посмотрюmany
. Похоже на довольно грубый пластырь.Статистика SQL Server содержит только гистограмму для ведущего столбца объекта статистики. Следовательно, вы можете создать отфильтрованную статистику, которая предоставляет гистограмму значений
Key2
, но только среди строк сKey1 = 1
. Создание этой отфильтрованной статистики в каждой таблице фиксирует оценки и приводит к ожидаемому поведению для тестового запроса: каждое новое объединение не влияет на окончательную оценку количества элементов (подтверждено в SQL 2016 SP1 и SQL 2017).Без этой отфильтрованной статистики SQL Server будет использовать более эвристический подход к оценке мощности вашего объединения. В следующем техническом документе содержатся подробные высокоуровневые описания некоторых эвристик, используемых SQL Server: Оптимизация планов запросов с помощью SQL Server 2014 Cardinality Estimator .
Например, добавление
USE HINT('ASSUME_JOIN_PREDICATE_DEPENDS_ON_FILTERS')
подсказки к вашему запросу изменит эвристику включения соединения, чтобы предположить некоторую корреляцию (а не независимость) междуKey1
предикатом иKey2
предикатом соединения, что может быть полезным для вашего запроса. Для окончательного тестового запроса эта подсказка увеличивает оценку мощности с1,175
до7,551
, но все еще немного стесняется правильной20,000
оценки строки, полученной с помощью отфильтрованной статистики.Другой подход, который мы использовали в подобных ситуациях, заключается в извлечении соответствующего подмножества данных в таблицы #temp. Особенно теперь, когда более новые версии SQL Server больше не с готовностью записывают таблицы #temp на диск , у нас были хорошие результаты с этим подходом. Ваше описание объединения «многие ко многим» подразумевает, что каждая отдельная таблица #temp в вашем случае будет относительно небольшой (или, по крайней мере, меньше, чем конечный набор результатов), поэтому этот подход может стоить попробовать.
источник
Key1
значение в каждой таблице. Теперь у нас их тысячи.Досягаемость Нет реальной основы, кроме как попробовать.
источник