Почему инструкция SELECT должна составлять 45% стоимости плана выполнения в SQL Server 2008?

У меня есть запрос, в котором я выбираю несколько столбцов из каждой из 5 левых внешних соединенных таблиц.

Я составил план выполнения в SQL Server 2008, и в основном сканирование таблиц выполняется для всех объединенных таблиц, но для них стоимость составляет 0% - я предполагаю, потому что в этих таблицах не так много записей.

Затем на последних 2 шагах плана выполнения (последнее объединение слиянием всех таблиц и фактический оператор SELECT) говорится, что 55% стоимости составляет объединение слиянием, а 45% стоимости - выбор.

Мне это кажется странным ... почему эти последние 2 шага «собрать все воедино» так дорого обходятся? Я думал, что все эти операции сканирования таблиц или сортировки будут иметь более существенные затраты.

Я пытаюсь получить одну "итоговую" запись из всех этих таблиц ... может быть, я придерживаюсь неправильного подхода, соединяя все слева?

Обновлено с помощью SQL

SELECT
/* Names */
NM.EMPLID, NM.NAME_PREFIX, NM.LAST_NAME, NM.FIRST_NAME, NM.MIDDLE_NAME, NM.NAME_SUFFIX,
/* Directory Info */
DIR_PERSON.BIRTH_DT,
/* PERSDATA */
PERS.SEX, PERS.HIGHEST_EDUC_LVL,
/* DIVERS.ETHNIC */
ETHNIC.ETHNIC_GRP_CD,
/* TENURE */
TENURE.EMPLID, TENURE.TENURE_STATUS, TENURE.EG_GRANTED_DT, TENURE.EG_TENURE_HOME, 
TENURE.EG_TRACK_HIRE_DT, TENURE.EG_MAND_REVW_DT, TENURE.CODE,
/* VISA */
VISA.VISA_PERMIT_TYPE

FROM NAMES NM

/* ----- Table Joins ----- */
/* Directory Join */
LEFT OUTER JOIN DIR_PERSON ON DIR_PERSON.ID = NM.EMPLID

/* PERS_DATA Join */
LEFT OUTER JOIN PERS ON PERS.EMPLID = NM.EMPLID
AND PERS.EFFDT =(   SELECT MAX(PERS_CURRENT.EFFDT) FROM PERS_CURRENT 
                    WHERE PERS.EMPLID = PERS_CURRENT.EMPLID 
                    AND PERS_CURRENT.EFFDT <= GETDATE())
/* ETHNIC Join */                       
LEFT OUTER JOIN  ETHNIC ON ETHNIC.EMPLID = NM.EMPLID
AND ETHNIC.PRIMARY_INDICATOR = 'Y'

/* TENURE Join */
LEFT OUTER JOIN TENURE ON TENURE.EMPLID = NM.EMPLID

/* VISA Join */
LEFT OUTER JOIN VISA ON VISA.EMPLID = NM.EMPLID
AND VISA.EFFDT = (  SELECT MAX(VISA_CURRENT.EFFDT) FROM VISA_CURRENT
                    WHERE VISA.EMPLID = VISA_CURRENT.EMPLID 
                    AND VISA_CURRENT.EFFDT <= GETDATE())

/* ----- End Table Joins ----- */       

WHERE   NM.NAME_TYPE = 'PRI' 
    AND NM.EFFDT = (SELECT MAX(NM_CURRENT.EFFDT) FROM NM_CURRENT 
                    WHERE NM.EMPLID = NM_CURRENT.EMPLID 
                    AND NM.NAME_TYPE = NM_CURRENT.NAME_TYPE 
                    AND NM_CURRENT.EFFDT <= GETDATE());

person chucknelson    schedule 25.11.2009    source источник
comment
Можете ли вы опубликовать .sqlplan (т.е. экспортированный план выполнения для запроса)?   -  person boydc7    schedule 25.11.2009
comment
SET SHOWPLAN_XML не поддерживается на сервере, на который я указываю (я полагаю, он был до 2005 года?), Поэтому я не смог получить экспортированный план, который выглядел бы хорошо.   -  person chucknelson    schedule 26.11.2009


Ответы (5)


ИДЕЯ ДЛЯ Ускорения

Я отредактировал ваш запрос (я не тестировал, поэтому могут быть опечатки), чтобы избавиться от подзапросов. Здесь вы сначала получаете все максимальные элементы (1 x количество empl), а затем запускаете основной выбор (1 x количество empl). Это изменяет ваш запрос с O (N ^ 3) на O (N), поэтому он должен быть быстрее.

Я сделал только два из них, третий должен быть понятен из этого примера:

WITH mVisa AS
(
 SELECT MAX(VISA_CURRENT.EFFDT) as max, VISA_CURRENT.EMPID as EMPLID
 FROM VISA_CURRENT
 WHERE VISA_CURRENT.EFFDT <= GETDATE()
 GROUP BY VISA_CURRENT.EMPLID
), mPers AS
(
 SELECT MAX(PERS_CURRENT.EFFDT) as max, PERS_CURRENT.EMPLID
 FROM PERS_CURRENT
 AND PERS_CURRENT.EFFDT <= GETDATE())
 GROUP BY PERS_CURRENT.EMPLID
)
SELECT
/* Names */
NM.EMPLID, NM.NAME_PREFIX, NM.LAST_NAME, NM.FIRST_NAME, NM.MIDDLE_NAME, NM.NAME_SUFFIX,
/* Directory Info */
DIR_PERSON.BIRTH_DT,
/* PERSDATA */
PERS.SEX, PERS.HIGHEST_EDUC_LVL,
/* DIVERS.ETHNIC */
ETHNIC.ETHNIC_GRP_CD,
/* TENURE */
TENURE.EMPLID, TENURE.TENURE_STATUS, TENURE.EG_GRANTED_DT, TENURE.EG_TENURE_HOME, 
TENURE.EG_TRACK_HIRE_DT, TENURE.EG_MAND_REVW_DT, TENURE.CODE,
/* VISA */
VISA.VISA_PERMIT_TYPE

FROM NAMES NM

/* ----- Table Joins ----- */
/* Directory Join */
LEFT OUTER JOIN DIR_PERSON ON DIR_PERSON.ID = NM.EMPLID

/* PERS_DATA Join */
LEFT JOIN mPers ON NM.EMPLID = mPers.EMPLID 
LEFT OUTER JOIN PERS ON PERS.EMPLID = NM.EMPLID
AND PERS.EFFDT = mPers.max
/* ETHNIC Join */                                       
LEFT OUTER JOIN  ETHNIC ON ETHNIC.EMPLID = NM.EMPLID
AND ETHNIC.PRIMARY_INDICATOR = 'Y'

/* TENURE Join */
LEFT OUTER JOIN TENURE ON TENURE.EMPLID = NM.EMPLID

/* VISA Join */
LEFT JOIN mVisa ON NM.EMPLID = mVisa.EMPLID
LEFT OUTER JOIN VISA ON VISA.EMPLID = NM.EMPLID
AND VISA.EFFDT = mVisa.max

/* ----- End Table Joins ----- */       

WHERE   NM.NAME_TYPE = 'PRI' 
        AND NM.EFFDT = (SELECT MAX(NM_CURRENT.EFFDT) FROM NM_CURRENT 
                                        WHERE NM.EMPLID = NM_CURRENT.EMPLID 
                                        AND NM.NAME_TYPE = NM_CURRENT.NAME_TYPE 
                                        AND NM_CURRENT.EFFDT <= GETDATE());
person Hogan    schedule 25.11.2009
comment
Спасибо, Хоган - сервер, на который я указываю, похоже, не поддерживает SHOWPLAN_XML, поэтому теперь я беспокоюсь, что он также не поддерживает CTE. Я попробую как можно скорее в понедельник ... в противном случае я попытаюсь поговорить с людьми, которые поддерживают эту БД и, надеюсь, оптимизируют некоторые вещи ... - person chucknelson; 26.11.2009
comment
SQL 2008? Тогда это сработает - showplan_xml требует dbo_owner или каких-то других прав. CTE теперь являются частью языка, должно быть хорошо. - person Hogan; 26.11.2009
comment
Если вы до 2005 года, вы можете вместо этого вставить в #tempname и ссылаться на эти таблицы. - person Hogan; 26.11.2009
comment
Круто, тогда я попробую ... похоже на CTE! Можете ли вы сказать, что мне еще предстоит многому научиться с этим? ;) Спасибо, Хоган! - person chucknelson; 26.11.2009
comment
Сервер - это SQL Server 2000, но временные таблицы, похоже, работают отлично. SELECT по-прежнему отображается как 49% стоимости, но запрос выполняется НАМНОГО быстрее с получением максимальных значений в подходе сначала к таблице. Спасибо за помощь! - person chucknelson; 30.11.2009
comment
В этом примере используются CTE. 45% должно быть потрачено на создание динамического индекса. Если вы создадите индекс заранее, он будет быстрее и вы увидите ожидаемые проценты. - person Hogan; 30.11.2009

Как сказал Кейд, сначала проверьте свои индексы.

Если индексы есть, убедитесь, что ваша статистика актуальна.

Если обе эти проблемы решаются, рассмотрите возможность рефакторинга ваших подзапросов в один или несколько CTE, а затем присоединитесь к ним по соответствующим критериям. Это не серебряная пуля, но, по моему опыту, CTE часто работают лучше, чем подзапросы.

person Brook    schedule 25.11.2009
comment
Точно! См. Мой комментарий Брук - O (N) вместо O (N ^ 2) или хуже - person Hogan; 25.11.2009
comment
+1 за то, что не был таким ленивым, как я, и действительно рефакторинг запроса :) - person Brook; 25.11.2009

45% чего-то маленького по-прежнему 45%. Трудно сказать, не увидев более подробностей, но я обнаружил, что заключительные этапы очень дороги при вставке в кластеризованную (по столбцам, отличным от IDENTITY) индексную таблицу или таблицу с большим количеством индексов.

При всех этих просмотрах таблиц - разве нет индексов?

person Cade Roux    schedule 25.11.2009
comment
Это просто прямой выбор, объединение данных из этих таблиц в одну итоговую строку для ключа (в данном случае сотрудника). У меня нет прав администратора для БД, и меня не было рядом, когда она была создана, но когда я смотрю на все эти таблицы через SQL Server, я не вижу ключей или индексов ни в одной из этих таблиц ... но я не уверен, что SQL Server показывает мне правду. - person chucknelson; 25.11.2009
comment
Я полностью там был. У вас может не быть хорошей статистики, поэтому план выполнения может быть неточным - соберите всю свою информацию и обратитесь к администратору базы данных. - person Cade Roux; 25.11.2009

Было бы полезно, если бы вы включили код, но если у вас, например, GROUP BY или ORDER BY, то это значительно добавит к запросу.

Если окончательный выбор - это большая таблица, а остальные не только крошечные, но и не очень часто используются в основной таблице, тогда вам нужно достичь 100% в какой-то части запроса, даже если это простая часть.

person James Black    schedule 25.11.2009

WHERE   NM.NAME_TYPE = 'PRI' 
        AND NM.EFFDT = (SELECT MAX(NM_CURRENT.EFFDT) FROM NM_CURRENT 
                                        WHERE NM.EMPLID = NM_CURRENT.EMPLID 
                                        AND NM.NAME_TYPE = NM_CURRENT.NAME_TYPE 
                                        AND NM_CURRENT.EFFDT <= GETDATE());

Ваши 45% здесь. Если вы создадите индекс для MN.NAME_TYPE и NM.EFFDT, вы увидите это падение на 45%.

Он может включать или не включать этот подзапрос в стоимость основного выбора - если это так, то именно в этом и заключается ваша проблема. ПОМНИТЕ, что он должен повторно запускать этот запрос для каждой строки.

См. Другой комментарий для рефакторинга соединения.

person Hogan    schedule 25.11.2009
comment
Чем больше я смотрю на это, тем больше мне интересно - что делают подзапросы ... у вас их 3 - они запускаются на каждой строке ... это ДОЛЖНО быть тем, что замедляет запрос. - person Hogan; 25.11.2009
comment
Любой подзапрос в соединении или WHERE возникает из-за того, что мне нужна последняя запись из этих таблиц для конкретного человека. - person chucknelson; 25.11.2009