16

"Is Persisted"가 으로 설정된 두 계산 열이있는 테이블이 있습니다. 그러나 u 리에서이를 g 용할 때, 실행 계획은 계획의 일부로 C 럼을 계산하는 데 사용 된 UDF를 표시합니다. 행 데이터가 추가/업데이트 될 때 UDF에 의해 열 데이터가 계산되므로 계획에 왜 포함됩니까?실행 계획에 지속되는 계산 열에 대한 사용자 정의 함수 호출이 포함되는 이유는 무엇입니까?

쿼리가 쿼리에 포함되어있을 때 쿼리의 속도가 매우 느리고 (> 30 초), 쿼리를 제외하면 번개 (< 1s)가 빠릅니다. 이것은 쿼리가 런타임에 실제로 열 값을 계산한다는 결론을 낳습니다.이 값은 지속되도록 설정 되었기 때문에 발생하지 않아야합니다.

여기에 뭔가가 있습니까?

업데이트 : 여기 계산 된 열을 사용하는 이유에 대한 정보가 있습니다.

우리는 스포츠 회사이며 전체 플레이어 이름을 단일 열에 저장하는 고객이 있습니다. 첫 번째 및/또는 성별로 플레이어 데이터를 검색 할 수 있도록해야합니다. 고맙게도 플레이어 이름 (성, 이름 (닉네임))에 일관된 형식을 사용하므로 구문 분석이 상대적으로 쉽습니다. 정규 표현식을 사용하여 이름 부분을 구문 분석하기 위해 CLR 함수를 호출하는 UDF를 작성했습니다. 따라서 CLR 함수를 호출하는 UDF를 호출하는 것은 매우 비용이 많이 듭니다. 그러나 그것은 단지 에 계속 사용 되었기 때문에 컬럼은 하루에 몇 번씩 만 데이터를 데이터베이스로 가져올 것이라고 생각했습니다.

+1

은 참고로 우리가 자신의 추가 정보를 응답 참조 마이크로 소프트 연결을 통해이 지역의 문제를 제기 : ([지속 계산 된 열 심각한 성능 문제를 조인] http://connect.microsoft .com/SQLServer/feedback/details/646700/심각한 성능 문제와 함께 지속 된 계산 된 열 및 조인) – redcalx

답변

23

이유는 쿼리 최적화 프로그램이 사용자 정의 함수의 비용을 계산하는 데별로 도움이되지 않기 때문입니다. 어떤 경우에는 필요하지 않은 디스크 읽기가 발생하지 않고 각 행의 기능을 완전히 재평가하는 것이 비용이 적게 듭니다.

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 
+1

와우, 훌륭한 설명을 주셔서 감사합니다. 계산 된 열에 UDF를 사용하는 추론을 포함하도록 질문을 업데이트했습니다. 그러나 적절한 인덱싱을 통해 문제를 완전히 해결할 수 있습니다. 감사! –

+1

맞습니다. 색인 생성은 절대적으로 도움이됩니다. 단일 계산 열에 인덱스를 만든 다음 해당 열에 대해서만 선택을 수행했으며 실행 계획은 인덱스를 사용하는 대신 각 행에 대해 UDF를 호출하여 값을 계산하도록 전체 테이블 스캔을 선택했습니다. 계산 된 열을 제거하고로드하는 동안 SSIS 패키지에서 데이터를 준비한 다음 일반 열로 덤핑했습니다. –

+1

Paul, CAST 또는 CONVERT와 같은 내장 함수에 해당됩니까? 나는 열을 공유하는 두 개의 데이터베이스에 테이블을 가지고 있지만 하나는 VARCHAR이고 다른 하나는 NUMERIC이며, 이들을 조인하는 많은 쿼리가있다. JOIN에서 암시 적 변환을 피하기 위해 계산 된 열 (및 적절한 인덱스)을 사용하고 싶습니다. 이게 합리적으로 보이나요? 이 경우, PERSISTED로 더 나을 가능성이 있습니까? –

관련 문제