Для функций безопасности на уровне строк применяются те же "правила", что и для представлений, так как они, похоже, работают аналогичным образом. Это означает, что с индексом companyid
CREATE INDEX IX_Member_OwnerCompanyId ON dbo.member (ownercompanyid)
и переписыванием функции следующим образом
CREATE FUNCTION dbo.fn_filterMember(@ownercompanyid AS INT)
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
SELECT 1 AS result
WHERE @ownercompanyid IS NULL
UNION ALL
SELECT 1
WHERE @ownercompanyid = CONVERT(INT, SESSION_CONTEXT(N'companyid'))
Мы приближаемся к оптимальным результатам, так как оптимизатор оценивает обе ветви независимо с одним из них, равным нулю, если значение SESSION_CONTEXT
равно NULL
. Если это не так, мы все равно получим довольно дорогой поиск и объединение для всех строк, которые соответствуют преобразованному SESSION_CONTEXT
(то есть тем, которые не NULL
). Это все еще немного быстрее, чем исходная функция на моей машине, однако, примерно на долю строк, которые не NULL
.
Я не вижу никакого способа оптимизировать это дальше, хотя это Стоит отметить, что это действительно только дорого, потому что фильтры не особенно избирательны. Кроме того, в отличие от простого сканирования таблицы с SELECT COUNT(*)
и без защиты на уровне строк, результирующий запрос не хочет распараллеливать, что еще больше снижает производительность. Я не знаю, в чем именно заключается проблема (обычно встроенные табличные функции не являются проблемой), но даже форсирование с помощью флага трассировки 8649 не поможет. Похоже, это общая проблема с функциями безопасности на уровне строк, потому что даже тривиальный постоянный фильтр, поддерживаемый индексом (WHERE @ownercompanyid IS NULL
), в некоторых случаях запрещает параллелизм.
Если вы не женат на SESSION_CONTEXT
, на самом деле есть более быстрая альтернатива: его старшая сестра CONTEXT_INFO
.
Недостатки CONTEXT_INFO
- это именно то, почему был изобретен SESSION_CONTEXT
, в том смысле, что это единый глобал (так что различные приложения легко попирают ноги друг друга), он имеет фиксированный тип BINARY(128) NOT NULL
, он не может быть защищен (поэтому ненадежные приложения могут его очистить), и его можно установить только с помощью SET CONTEXT_INFO
, который не принимает выражений или переменных.
Несмотря на все это, использование опции CONTEXT_INFO
стоит рассмотреть, так как оптимизатору это нравится гораздо лучше, чем его ключевому аналогу. То есть:
CREATE FUNCTION dbo.fn_filterMember(@ownercompanyid AS INT)
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
SELECT 1 _
WHERE @ownercompanyid IS NULL
OR @ownercompanyid = NULLIF(CONVERT(INT, CONVERT(BINARY(4), CONTEXT_INFO())), 0)
Нет UNION ALL
на этот раз, так как мы не хотим вызвать два сканирования в этом случае. Задайте либо SET CONTEXT_INFO 0
(чтобы "очистить" его), либо SET CONTEXT_INFO 1
, и теперь запросы снова выполняются быстро, поскольку параллелизм больше не запрещается. И хотя обычный индекс ускорит это, еще лучшим вариантом теперь является индекс columnstore:
CREATE NONCLUSTERED COLUMNSTORE INDEX IX_Member_OwnerCompanyId ON dbo.member (ownercompanyid)
Результирующие запросы выполняются настолько быстро, насколько это возможно, так как COUNT(*)
напрямую подается из columnstore, который практически сделан для этого. Конечно, в реальном приложении (а не в простом COUNT(*)
) хранилище columns может или не может улучшить ситуацию, но, по крайней мере, оно демонстрирует, что оптимизатор может его использовать (что не так, если используется SESSION_CONTEXT()
, когда он падает). обратно в режим обработки строк сразу после сканирования хранилища столбцов, сводя на нет преимущества).