【问题标题】:How to get MS SQL Server to transparently use a CHECKSUM/hash index?如何让 MS SQL Server 透明地使用 CHECKSUM/哈希索引?
【发布时间】:2012-06-18 13:38:31
【问题描述】:

SQL Server 似乎不会自动使用 CHECKSUM/哈希索引,除非 CHECKSUM 列明确包含在查询的搜索参数中。这是一个问题,因为我不控制查询表的应用程序,并且我可能不会破坏它们的性能。

有没有办法让 SQL Server 使用新的 CHECKSUM/hash 索引修改查询以包含新的 CHECKSUM/hash 列?

重现脚本

CREATE TABLE big_table
(
    id BIGINT IDENTITY CONSTRAINT pk_big_table PRIMARY KEY,
    wide_col VARCHAR(50),
    wide_col_checksum AS CHECKSUM(wide_col),
    other_col INT
)

CREATE INDEX ix_checksum ON big_table (wide_col_checksum)

插入一些测试数据:

SET NOCOUNT ON
DECLARE @count INT = 0
BEGIN TRANSACTION
WHILE @count < 10000
BEGIN
    SET @count = @count + 1
    INSERT INTO big_table (wide_col, other_col) 
    VALUES (SUBSTRING(master.dbo.fn_varbintohexstr(CRYPT_GEN_RANDOM(25)), 3, 50), @count)
    IF @count % 1000 = 0
    BEGIN
        COMMIT TRANSACTION
        BEGIN TRANSACTION
    END
END
COMMIT TRANSACTION

INSERT INTO big_table (wide_col, other_col) 
VALUES ('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 9999999)

旧版查询。导致聚集索引扫描 (BAD):

SELECT * FROM big_table 
WHERE wide_col = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'


更新了查询。导致非聚集索引搜索(良好):

SELECT * FROM big_table 
WHERE wide_col = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
AND wide_col_checksum = CHECKSUM('ABCDEFGHIJKLMNOPQRSTUVWXYZ')

背景

我的表非常大(数亿行),有几个索引(~ 20),所有这些都是必需的。一些索引列有点宽(约 50 个字节)并且几乎没有重复值。仅在相等时搜索列。表是不断插入的。

这是一个比较上述示例表中“正常”索引和 CHECKSUM/hash 索引的表,包括压缩和非压缩。来自 100 万行表的新重建索引的数据:

单独的页面压缩对样本数据非常无效(实际数据应该压缩得更好一些)。哈希索引实现了 4 倍的索引大小减少。哈希索引上的页面压缩实现了 6 倍的索引大小减小。

我使用哈希索引的目的是:

  1. 减少这些索引在内存中的大小,从而允许 SQL Server 在 RAM 中缓存更多部分,从而避免物理读取。
  2. 减少索引存储大小。
  3. 减少 INSERT 操作的索引 I/O。

【问题讨论】:

  • 也许你应该考虑数据压缩。它可以轻松完成所有这些事情。
  • 你需要某种INSTEAD OF SELECT 触发器,而且不存在这样的东西,所以很确定答案是“不”。
  • @Aaron 我添加了一个表格,比较了单独 PAGE 压缩的索引大小减少与 CHECKSUM 的大小减少。通过对哈希索引启用 PAGE 压缩,我们可以将原始宽索引的大小减少近 6 倍。
  • @MartinSmith 我想我找到了一种棘手的方法来建立一种INSTEAD OF SELECT,请参阅我的答案

标签: sql-server sql-server-2008 hash indexing database-performance


【解决方案1】:

如果您的应用程序查询:

SELECT * FROM big_table WHERE wide_col = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

您需要wide_col 上的索引,而不是wide_col_checksum 上的索引。

SQL Server 将索引存储为 B 树。正如@MartinSmith 所建议的那样,减少索引中列的大小确实会减少内存和磁盘占用。

【讨论】:

  • 索引的上层也包含索引键(每个包含索引键和页面指针的下层页面一行),因此将键的宽度从 50 字节减少到 4 字节可能是进步很大。
  • @MartinSmith:SQL Server 内部同意你的看法。我原以为 SQL Server 只存储 N+1 个字节,其中 N 是左右页面共有的字节数。
【解决方案2】:

SQL Server 不会使用校验和/哈希索引自动启动。查询需要使用散列列供sql server 考虑使用索引。 因此,我看不到您如何实现对查询进行更改的目标。 然而,这是一个有趣的问题,可能是对 SQL Server 的一个很好的功能请求。

【讨论】:

    【解决方案3】:

    我有一个解决方案给你,这是一项艰巨的任务!

    您可以重命名您的表格,然后使用表格的名称创建一个视图,然后在该视图中执行此操作。

    想法是用视图捕获对表的调用,如果没有直接过滤wide_col 或使用ix_checksum 索引对应的记录,则在视图内部返回所有记录。

    我使用sys.dm_exec_requestssys.dm_exec_sql_text 来获取用户想要的查询文本,然后通过一点解析提取wide_col 列的参数及其CHECKSUM()NULL(如果没有参数)找到了。

    之后,我使用该校验和(如果存在)提取记录的id

    如果查询中没有请求过滤器,我使用UNION ALL 运算符将所有记录添加到结果集中。

    这很棘手,但很有效!

    警告!
    我只是做了一点解析以从查询中获取参数,您应该检查您的查询以查看它是否正确并在需要时进行调整。

    -- rename the table
    exec sp_rename big_table, _big_table;
    go
    
    drop view big_table
    go
    
    -- create the view with the name of the table
    create view big_table
    as
    with
    q as ( -- extract the query text
        SELECT SUBSTRING(dest.text, (dem.statement_start_offset+2)/2, CASE WHEN dem.statement_end_offset=-1 THEN 8000 ELSE (dem.statement_end_offset-dem.statement_start_offset+4)/2 END) current_statement
        FROM   sys.dm_exec_requests dem CROSS APPLY sys.dm_exec_sql_text(dem.sql_handle) dest  WHERE  session_id = @@SPID
    ),
    f as ( -- do some parsing to get WHERE condition
        select 
            REPLACE(REPLACE(REPLACE(REPLACE(
                SUBSTRING(current_statement, nullif(patindex('%WHERE%wide_col%=%''%''%', current_statement), 0)+5, 8000)
            , CHAR(9), ' '), CHAR(10), ' '), CHAR(13), ' '), ' ', '') par 
            from q 
            where current_statement like '%WHERE%wide_col%=%''%''%'
    ),
    r as ( -- some more parsig to get wide_col filter
        select SUBSTRING(par, 1, charindex('''', par)-1) par
        from (
            select SUBSTRING(par, patindex('%wide_col=''%''%', par)+LEN('wide_col')+2, 8000) par
            from f
            where par like '%wide_col=''%''%'
        ) r
    ),
    p as ( -- calc the checksum of the parameter
        select par, iif(par is null, null, CHECKSUM(par)) chk 
        from r
    ),
    x as ( -- lookup the id of the searched record
        select m.id 
        from _big_table m 
        where wide_col_checksum = (select chk from p)),
    z as ( -- test if a parameter was found (flag for normal operation)
        select COUNT(*) n 
        from p 
        where chk is not null
    )
    
    -- this is the fast output for searched record
    select m.*
    from _big_table m, x
    where (m.id = x.id) --OR (x.id is null) 
    
    union all
    
    -- this is the normal output for all other conditions
    select m.*
    from _big_table m, z
    where z.n = 0
    

    享受

    【讨论】:

    • 虽然解决方案令人印象深刻,但我想这只是个玩笑。我不建议在真实服务器上使用它,因为它对将要执行的查询做出了很多假设,并且它仅适用于某些特定查询(对于其他查询,它返回错误的结果)。
    • @RazvanSocol 你是对的,这是一个演示,它应该谨慎使用和知识并且还需要以用户查询为模型
    【解决方案4】:

    在大多数排序规则中,两个查询可以提供不同的结果,因为'A'='a',但CHECKSUM('A') 不等于CHECKSUM('a')。即使在 CS_AS 或 BIN 排序规则中,尾随空格也可能是个问题。所以这就是为什么 SQL Server 不能自动使用这样的索引。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-19
      • 2014-03-30
      相关资源
      最近更新 更多