SQL Server가 간단한 bijection에서 인덱스를 사용하지 못함


11

이것은 또 다른 쿼리 최적화 프로그램 수수께끼입니다.

어쩌면 나는 쿼리 최적화 프로그램을 과대 평가하거나 무언가를 놓치고있을 수도 있습니다.

나는 간단한 테이블이있다

CREATE TABLE [dbo].[MyEntities](
  [Id] [uniqueidentifier] NOT NULL,
  [Number] [int] NOT NULL,
  CONSTRAINT [PK_dbo.MyEntities] PRIMARY KEY CLUSTERED ([Id])
)

CREATE NONCLUSTERED INDEX [IX_Number] ON [dbo].[MyEntities] ([Number])

인덱스와 수천 개의 행이 있으며 Number0, 1 및 2의 값으로 균등하게 분배됩니다.

이제이 쿼리 :

SELECT * FROM
    (SELECT
        [Extent1].[Number] AS [Number],
        CASE
        WHEN (0 = [Extent1].[Number]) THEN 'one'
        WHEN (1 = [Extent1].[Number]) THEN 'two'
        WHEN (2 = [Extent1].[Number]) THEN 'three'
        ELSE '?'
        END AS [Name]
        FROM [dbo].[MyEntities] AS [Extent1]
        ) P
WHERE P.Number = 0;

인덱스 IX_Number는 예상대로 검색합니다 .

where 절이

WHERE P.Name = 'one';

그러나 스캔이됩니다.

case-clause는 명백히 bijection이므로 이론상 첫 번째 쿼리 계획을 두 번째 쿼리에서 빼는 최적화가 가능해야합니다.

그것은 또한 학문적이지 않습니다 :이 쿼리는 열거 형 값을 각각의 친숙한 이름으로 번역하여 영감을 얻었습니다.

쿼리 최적화 프로그램 (특히 SQL Server의 최적화 프로그램)에서 기대할 수있는 것을 알고있는 누군가의 의견을 듣고 싶습니다. 단순히 너무 많이 기대하고 있습니까?

쿼리의 약간의 변형으로 인해 최적화가 갑자기 시작되는 경우가 있었기 때문에 묻습니다.

Sql Server 2016 Developer Edition을 사용하고 있습니다.

답변:


18

나는 단순히 너무 많이 기대하고 있는가?

예. 최소한 현재 버전의 제품.

SQL Server는 CASE계산 된 열의 결과가 'one'다음 [Extent1].[Number]이어야 한다는 것을 발견하기 위해 명령문을 선택하지 않고 리버스 엔지니어링 하지 않습니다 0.

당신은 당신의 술어를 숙고 할 수있게 작성해야합니다. 거의 항상 형식으로되어 있습니다. basetable_column_name comparison_operator expression.

약간의 편차만으로도 Sargability가 손상됩니다.

WHERE P.Number + 0 = 0;

CASE표현식 보다 단순화하는 것이 더 간단하더라도 인덱스 탐색을 사용하지 않습니다 .

문자열 이름을 검색하고 검색 번호를 얻으려면 이름과 번호가 포함 된 매핑 테이블이 필요하고 쿼리에서 조인하면 계획에 매핑 테이블에 대한 검색이 있고 관련 검색이있을 수 있습니다. 에 [dbo].[MyEntities]반환 된 번호로 먼저 추구합니다.


6

열거 형을 사례 진술로 투영하지 마십시오. 다음과 같이 파생 테이블로 투영하십시오.

SELECT * FROM
   (SELECT
      [Extent1].[Number] AS [Number],
      enum.Name
   FROM
      [dbo].[MyEntities] AS [Extent1]
      LEFT JOIN (VALUES
         (0, 'one'),
         (1, 'two'),
         (2, 'three')
      ) enum (Number, Name)
         ON Extent1.Number = enum.Number
   ) P
WHERE
   P.Name = 'one';

더 나은 결과를 얻을 것으로 생각됩니다. (이름 ?이 누락되었을 때 이름을 변환하지 않았기 때문에 성능 향상을 방해 할 수 있습니다. 그러나 테이블에 WHERE술부를 놓기 위해 외부 쿼리 내에서 절을 이동 enum하거나 테이블에서 두 개의 열을 리턴 할 수 있습니다 내부 쿼리 (하나는 술어 용이고 다른 하나는 표시되는 것입니다. 여기서 술어는 NULL일치하는 열거 형 값이 없을 때입니다.)

그러나 [Extent1]거기에 있기 때문에 Entity Framework 또는 Linq-To-SQL과 같은 ORM을 사용하고 있다고 생각합니다. 이러한 투영을 기본적으로 수행하는 방법을 안내 할 수는 없지만 다른 기술을 사용할 수 있습니다.

내 프로젝트 중 하나에서 enum 값을 데이터베이스에 병합하는 사용자 지정 빌드 클래스를 통해 데이터베이스의 실제 테이블에 코드 enum 값을 반영했습니다. (당신은 열거 형 값을 명시 적으로 나열하고 테이블을 검토하지 않고는 절대 삭제할 수 없으며 절대로 변경할 수는 없지만 규칙을 존중해야합니다. 현재 설정에서 적어도 일부를 관찰해야합니다) .

이제는 Identifier많은 다른 구체적인 하위 클래스가 있는 열거 가능한 기본 클래스를 사용하고 있었지만 일반 바닐라 열거 형으로 수행 할 수 없었습니다. 사용 예는 다음과 같습니다.

new EnumOrIdentifierProjector<CodeClassOrEnum, PrivateDbDtoObject>(
   _sqlConnector.Connection,
   "dbo.TableName",
   "PrimaryKeyId",
   "NameColumnName",
   dtoObject => dtoObject.PrimaryKeyId,
   dtoObject => dtoObject.NameField,
   EnumerableOfIdentifierOrTypeOfEnum
)
   .Populate();

데이터베이스 값을 쓰고 읽는 데 필요한 모든 정보를 전달했음을 알 수 있습니다. (현재 요청에 존재하는 모든 값이 포함되어 있지 않을 수 있으므로 현재로드 된 세트뿐만 아니라 데이터베이스에서 추가 값을 반환해야합니다. 또한 데이터베이스에 ID를 할당하도록했습니다. 그걸 원해)

모든 열거 값을 안정적으로 가질 수있는 시작시 한 번만 읽거나 쓰는 테이블이 있으면 다른 테이블과 마찬가지로 테이블에 조인하면 성능이 좋아야합니다.

이러한 아이디어가 귀하가 개선하기에 충분하기를 바랍니다.


예, EntityFramework를 사용하며 솔루션이 실제로 최적의 세계에 있어야하는 곳이 있습니다. 그 전에 귀하의 제안은 내가 믿는 가장 좋은 해결 방법 중 하나입니다.
John

5

나는 당신이 일반적으로 옵티 마이저에 관심이 있지만 SQL Server에 특별한 관심이 있다고 질문을 해석합니다. db2 LUW V11.1을 사용하여 시나리오를 테스트했습니다.

]$ db2 "create table myentities ( id int not null, number int not null )"
]$ db2 "create index ix_number on myentities (number)"
]$ db2 "insert into myentities (id, number) with t(n) as ( values 0 union all select n+1 from t where n<10000) select n, mod(n,3) from t"

DB2의 최적화 프로그램은 두 번째 쿼리를 첫 번째 쿼리로 다시 작성합니다.

Original Statement:
------------------
SELECT 
  * 
FROM 
  (SELECT 
     number,

   CASE 
   WHEN (0 = Number) 
   THEN 'one' 
   WHEN (1 = Number) 
   THEN 'two' 
   WHEN (2 = Number) 
   THEN 'three' 
   ELSE '?' END AS Name 
   FROM 
     MyEntities
  ) P 
WHERE 
  P.name = 'one'


Optimized Statement:
-------------------
SELECT 
  Q1.NUMBER AS "NUMBER",

CASE 
WHEN (0 = Q1.NUMBER) 
THEN 'one' 
WHEN (1 = Q1.NUMBER) 
THEN 'two' 
WHEN (2 = Q1.NUMBER) 
THEN 'three' 
ELSE '?' END AS "NAME" 
FROM 
  LELLE.MYENTITIES AS Q1 
WHERE 
  (0 = Q1.NUMBER)

계획은 다음과 같습니다.

Access Plan:
-----------
        Total Cost:             33.5483
        Query Degree:           1


      Rows 
     RETURN
     (   1)
      Cost 
       I/O 
       |
      3334 
     IXSCAN
     (   2)
     33.1861 
     4.66713 
       |
      10001 
 INDEX: LELLE   
    IX_NUMBER
       Q1

다른 옵티 마이저에 대해서는 잘 모르지만 DB2 옵티마이 저는 경쟁사들 사이에서도 꽤 좋은 것으로 여겨집니다.


신난다. "최적화 된 진술"의 출처를 밝힐 수 있습니까? db2 자체가이를 다시 제공합니까? -또한 계획을 읽는 데 어려움이 있습니다. 나는 수행을 "IXSCAN"가지고 하지 ,이 경우 평균 인덱스 스캔을?
John

1
명령문을 설명하도록 DB2에 지시 할 수 있습니다. 수집 된 정보는 테이블 세트에 저장되며 시각적 Explain을 사용하거나이 경우 유틸리티 db2exfmt를 사용하거나 고유 한 유틸리티를 작성할 수 있습니다. 또한 명세서를 모니터하고 계획의 예상 카디널리티를 실제 계획과 비교할 수 있습니다. 이 계획에서 실제로 인덱스 스캔 (IXSCAN)이고이 연산자의 예상 출력이 3334 행임을 알 수 있습니다. SQL 서버에서 이것이 나쁜가요? 시작 키와 중지 키를 알고 있으므로 DB2의 관련 행만 스캔합니다.
Lennart

따라서 스캔이라고하는 것은 검색과 관련이 있으며, 솔직히 말하면 Sql Server의 동등한 계획 설명은 때로는 검색과 관련된 스캔을 호출하고 다른 경우에는이를 검색이라고 부릅니다. 나는 무엇이 무엇인지 이해하기 위해 항상 행 수를 살펴볼 필요가 있습니다. db2의 출력에는 3334가 분명히 있기 때문에 내가 바랐던 것을 확실히합니다. 매우 흥미로운.
John

네, 때로는 혼란 스럽습니다. 각 운영자가 진행중인 상황을 실제로 이해하려면보다 자세한 정보를 살펴 봐야합니다.
Lennart

0

이 특정 쿼리에서 CASE명령문을 갖는 것은 꽤 어리 석습니다 . 특정 사례로 필터링하고 있습니다! 아마도 이것은 당신이 주어진 특정 예제 쿼리의 세부 사항 일 것입니다. 그렇지 않으면 동등한 쿼리 결과를 얻기 위해이 쿼리를 작성할 수 있습니다.

SELECT
    [Extent1].[Number] AS [Number],
    'one' AS [Name]
FROM [dbo].[MyEntities] AS [Extent1]
WHERE [Extent1].[Number] = 0;

이것은 정확히 동일한 결과 집합을 제공하며, CASE어쨌든 명령문 에서 이미 값을 하드 코딩하기 때문에 유지 보수성을 잃지 않습니다.


1
요점을 놓치고 있다고 생각합니다. 백엔드 코드베이스에서 문자열 표현을 통해 열거 형으로 작동하는 SQL에서 생성됩니다. SQL을 투영하는 코드가 쿼리에 대한 폭력을하고 있습니다. 그가 SQL을 직접 작성했다면 더 나은 쿼리를 작성할 수있을 것이라고 확신합니다. 따라서 CASEORM이 그런 종류의 일을하기 때문에 진술을 하는 것은 어리석지 않습니다 . 바보 같은 것은 당신이 문제의 간단한 측면을 인식하지 못했다는 것입니다 ... (간접적으로 두뇌가없는
이유는

@ErikE 어쨌든 C #을 가정하고 열거 형 의 숫자 값을 사용할 수 있기 때문에 여전히 바보 같은 종류입니다 . (SQL Server를 사용한다고 가정하면 상당히 안전한 가정입니다.)
jpmc26

그러나 유스 케이스가 무엇인지 전혀 모릅니다. 아마도 숫자 값으로 전환하는 것이 큰 변화 일 것입니다. 아마도 열거 형이 기존의 거대한 코드베이스에 개조되었을 수 있습니다. 지식없이 비판하는 것은 말도 안됩니다.
ErikE

@ErikE 말도 안된다면 왜 그렇게합니까? =) 유스 케이스가 질문의 예처럼 간단하다면 (내 답변의 서문에 명확하게 명시되어 있음) CASE진술은 단점없이 완전히 제거 될 수 있다고 지적했습니다 . 중 물론 이 알 수없는 요인이 될 수 있지만 지정되지 않은 것 수 있습니다.
jpmc26

나는 당신의 대답의 사실적인 부분, 주관적으로 특징 짓는 부분에 반대하지 않습니다. 내가 지식없이 비판하고 있는지 여부에 관해서는, 나는 철저하게 깨끗한 논리를 사용하지 못했거나 명백히 거짓 인 가정을 한 방법을 이해해야한다.
ErikE
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.