이유는 쿼리 최적화 프로그램이 사용자 정의 함수의 비용을 계산하는 데별로 도움이되지 않기 때문입니다. 어떤 경우에는 필요하지 않은 디스크 읽기가 발생하지 않고 각 행의 기능을 완전히 재평가하는 것이 비용이 적게 듭니다.
SQL Server의 비용 모델은 함수의 구조를 검사하지 않기 때문에 실제 비용이 얼마인지 알 수 없으므로 옵티마이 저가 정확한 정보를 갖고 있지 않습니다. 함수가 임의적으로 복잡 할 수 있으므로 원가 계산이이 방법으로 제한된다는 것은 이해할 수 있습니다. 이 효과는 스칼라 및 다중 문 테이블 반환 함수에 대해 최악입니다. 행 당 호출 비용이 극히 높기 때문입니다.
쿼리 계획을 검사하여 옵티마이 저가 지속 된 값을 사용하는 대신 기능을 재평가하기로 결정했는지 여부를 알 수 있습니다. 정의 된 값 목록에 함수 이름에 대한 명시 적 참조가있는 Compute 스칼라 반복기가있는 경우 함수는 행당 한 번 호출됩니다. 정의 된 값 목록이 대신 열 이름을 참조하면 함수가 호출되지 않습니다.
제 조언은 일반적으로 계산 된 열 정의에서 함수를 사용하지 않는 것입니다.
아래의 복제 스크립트는 문제를 보여줍니다. 테이블에 대해 정의 된 PRIMARY KEY는 클러스터되지 않기 때문에 지속 된 값을 가져 오려면 인덱스 또는 테이블 스캔의 책갈피 조회가 필요합니다. 옵티마이 저는 책갈피 조회 또는 표 스캔 비용이 들지 않고 색인에서 함수의 소스 컬럼을 읽고 행당 함수를 다시 계산하는 것이 더 저렴하다고 결정합니다.
지속 된 열을 인덱싱하면이 경우 쿼리 속도가 빨라집니다. 일반적으로 옵티마이 저는 기능을 다시 계산하지 못하게하는 액세스 경로를 선호하지만 결정은 비용 기반이므로 인덱스 된 경우에도 각 행에 대해 함수를 다시 계산할 수 있습니다. 그럼에도 불구하고 옵티 마이저에 '확실하고 효율적인 액세스 경로를 제공하면이를 피하는 데 도움이됩니다.
색인을 생성하려면 열 이 아니고이 아니어야합니다. 이것은 매우 흔한 오해입니다. 열을 지속하면 이 필요합니다. 부동 소수점 산술 또는 값을 사용하는 곳이 부정확합니다. 이 경우 열을 지속하면 값이 추가되지 않고 기본 테이블의 저장 요구 사항이 확장됩니다.
폴 화이트
-- An expensive scalar function
CREATE FUNCTION dbo.fn_Expensive(@n INTEGER)
RETURNS BIGINT
WITH SCHEMABINDING
AS
BEGIN
DECLARE @sum_n BIGINT;
SET @sum_n = 0;
WHILE @n > 0
BEGIN
SET @sum_n = @sum_n + @n;
SET @n = @n - 1
END;
RETURN @sum_n;
END;
GO
-- A table that references the expensive
-- function in a PERSISTED computed column
CREATE TABLE dbo.Demo
(
n INTEGER PRIMARY KEY NONCLUSTERED,
sum_n AS dbo.fn_Expensive(n) PERSISTED
);
GO
-- Add 8000 rows to the table
-- with n from 1 to 8000 inclusive
WITH Numbers AS
(
SELECT TOP (8000)
n = ROW_NUMBER() OVER (ORDER BY (SELECT 0))
FROM master.sys.columns AS C1
CROSS JOIN master.sys.columns AS C2
CROSS JOIN master.sys.columns AS C3
)
INSERT dbo.Demo (N.n)
SELECT
N.n
FROM Numbers AS N
WHERE
N.n >= 1
AND N.n <= 5000
GO
-- This is slow
-- Plan includes a Compute Scalar with:
-- [dbo].[Demo].sum_n = Scalar Operator([[dbo].[fn_Expensive]([dbo].[Demo].[n]))
-- QO estimates calling the function is cheaper than the bookmark lookup
SELECT
MAX(sum_n)
FROM dbo.Demo;
GO
-- Index the computed column
-- Notice the actual plan also calls the function for every row, and includes:
-- [dbo].[Demo].sum_n = Scalar Operator([[dbo].[fn_Expensive]([dbo].[Demo].[n]))
CREATE UNIQUE INDEX uq1 ON dbo.Demo (sum_n);
GO
-- Query now uses the index, and is fast
SELECT
MAX(sum_n)
FROM dbo.Demo;
GO
-- Drop the index
DROP INDEX uq1 ON dbo.Demo;
GO
-- Don't persist the column
ALTER TABLE dbo.Demo
ALTER COLUMN sum_n DROP PERSISTED;
GO
-- Show again, as you would expect
-- QO has no option but to call the function for each row
SELECT
MAX(sum_n)
FROM dbo.Demo;
GO
-- Index the non-persisted column
CREATE UNIQUE INDEX uq1 ON dbo.Demo (sum_n);
GO
-- Fast again
-- Persisting the column bought us nothing
-- and used extra space in the table
SELECT
MAX(sum_n)
FROM dbo.Demo;
GO
-- Clean up
DROP TABLE dbo.Demo;
DROP FUNCTION dbo.fn_Expensive;
GO
은 참고로 우리가 자신의 추가 정보를 응답 참조 마이크로 소프트 연결을 통해이 지역의 문제를 제기 : ([지속 계산 된 열 심각한 성능 문제를 조인] http://connect.microsoft .com/SQLServer/feedback/details/646700/심각한 성능 문제와 함께 지속 된 계산 된 열 및 조인) – redcalx