【问题标题】:SQL Server stopped using INDEX after COLLATION change在 COLLATION 更改后 SQL Server 停止使用 INDEX
【发布时间】:2018-05-19 01:27:34
【问题描述】:

更新:问题已重写,因此某些 cmets 可能不再相关。

更改表中的排序规则后,我们遇到了索引使用方面的问题。出现了很多扫描。默认数据库排序规则是 SQL_Latin1_General_CP1_CI_AS。现在让我们看一下这个例子:

IF OBJECT_ID('colltest') > 0 DROP TABLE CollTest;

CREATE TABLE dbo.CollTest
(
    cs       CHAR(8) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL
  , ci       CHAR(8) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL
  , cs_latin CHAR(8) COLLATE Latin1_General_CS_AS NOT NULL
  , ci_latin CHAR(8) COLLATE Latin1_General_CI_AS NOT NULL
);

CREATE INDEX ix_cs ON dbo.CollTest (cs);

CREATE INDEX ix_ci ON dbo.CollTest (ci);

CREATE INDEX ix_cs_latin ON dbo.CollTest (cs_latin);

CREATE INDEX ix_ci_latin ON dbo.CollTest (ci_latin);

WITH q (n) AS (SELECT 1
               UNION ALL
               SELECT 1
               UNION ALL
               SELECT 1
               UNION ALL
               SELECT 1
               UNION ALL
               SELECT 1
               UNION ALL
               SELECT 1
               UNION ALL
               SELECT 1
               UNION ALL
               SELECT 1
               UNION ALL
               SELECT 1
               UNION ALL
               SELECT 1)
   , q100 (n) AS (SELECT 1 FROM q a, q b)
   , q10000 (n) AS (SELECT 1 FROM q100 a, q100 b)
   , q100000 (n) AS (SELECT 1 FROM q a, q10000 b)
INSERT INTO dbo.CollTest
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) step
     , ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) step
     , ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) step
     , ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) step
  FROM q100000;

以下是以下查询的解释计划:

SELECT cs FROM dbo.colltest WHERE cs = CAST('1000000' AS NVARCHAR(MAX))
SELECT ci FROM dbo.colltest WHERE ci = CAST('1000000' AS NVARCHAR(MAX))
SELECT cs_latin FROM dbo.colltest WHERE cs_latin = CAST('1000000' AS NVARCHAR(MAX))
SELECT ci_latin FROM dbo.colltest WHERE ci_latin = CAST('1000000' AS NVARCHAR(MAX))

所以它表明当 COLLATION 是 SQL_* 时它使用扫描,当 Latin* 时它使用搜索。这是为什么呢?

【问题讨论】:

  • SELECT 上有一个小感叹号,那是什么意思?您可能在流程中的某处进行了隐式类型转换。
  • 抱怨从整数转换为 nvarchar(max)。两个查询都是一样的。
  • 执行计划的第一张图片中没有显示?像这样的强制转换可能会导致它不寻找索引而是扫描它们。
  • 我明白这一点。但为什么它只与第二个查询有关。此列在两个表中都是 CHAR(8),区别仅在于排序规则。
  • 转换为 nvarchar(max) 是与索引不同的排序规则,因此它正在扫描。尝试像CAST( 42421164 AS nvarchar(max)) COLLATE XXXXXX 这样对索引进行适当的排序。此外,如果该列是 char(8),我将转换为该列而不是 nvarchar(max)。特别是如果感叹号标记是类型转换警告。

标签: sql-server indexing sql-server-2014 collation query-performance


【解决方案1】:

这在Comparing SQL collations to Windows collations中有解释

对于 Windows 排序规则,非 Unicode 数据的比较是 通过使用与 Unicode 数据相同的算法来实现。 ... 在 SQL 排序规则中,SQL Server 定义了不同的比较语义 对于非 Unicode 数据

NVARCHAR 的数据类型优先级高于 VARCHAR,因此当您将 VARCHAR 列与 NVARCHAR 进行比较时,需要隐式转换该列。

示例数据

DECLARE @T TABLE
(
SQL_CollationVC   VARCHAR(1) COLLATE SQL_Latin1_General_CP1_CI_AS,
Win_CollationVC   VARCHAR(1) COLLATE Latin1_General_CS_AS,
INDEX SQL_CollationVC(SQL_CollationVC),
INDEX Win_CollationVC(Win_CollationVC)
);

INSERT INTO @T
VALUES
(N'¹',N'¹'),
(N'½',N'½'),
(N'¾',N'¾'),
(N'0',N'0'),
(N'1',N'1');

查询

SELECT Win_CollationVC
FROM @T
WHERE Win_CollationVC = N'1'

能够执行dynamic seek 并调用GetRangeThroughConvert 将谓词转换为varchar 索引上的可查找索引范围。

对于查询

SELECT SQL_CollationVC
FROM @T
WHERE SQL_CollationVC = N'1'

你看到了扫描

请注意,这会返回两个结果 ¹1

还要注意这个结果

SELECT SQL_CollationVC
FROM @T
ORDER BY SQL_CollationVC

该列的索引将按照上面返回的顺序存储。

+-----------------+
| SQL_CollationVC |
+-----------------+
| ¹               |
| ½               |
| ¾               |
| 0               |
| 1               |
+-----------------+

1¹ 不会在索引中彼此相邻存储,因此无法将谓词 SQL_CollationVC = N'1' 转换为对该索引的简单搜索。

【讨论】:

    猜你喜欢
    • 2011-02-16
    • 2015-12-02
    • 1970-01-01
    • 2016-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多