看起来SQL Server不会自动使用CHECKSUM /哈希索引,除非CHECKSUM列明确包含在查询的搜索参数中。这是一个问题,因为我不控制查询表的应用程序,我可能不会破坏它们的性能。
有没有办法让SQL Server使用新的CHECKSUM /哈希索引而不修改查询以包含新的CHECKSUM /哈希列?
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'
更新的查询。导致NonClustered Index Seek(好):
SELECT * FROM big_table
WHERE wide_col = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
AND wide_col_checksum = CHECKSUM('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
我的表非常大(数亿行),有几个索引(~20),所有这些都是必需的。一些索引列有点宽(~50个字节)并且具有很少的重复值。仅在相等性上搜索列。桌子不断插入。
下面是一个表格,比较上面样本表中的“普通”索引和CHECKSUM /哈希索引,包括压缩和非压缩。来自具有100万行的表的新重建索引的数据:
单独的页面压缩对样本数据非常无效(实际数据应该更好地压缩)。哈希索引实现了4X索引大小的减少。哈希索引上的页面压缩可以减少6倍的索引大小。
我使用哈希索引的目的是:
如果您的应用程序查询
SELECT * FROM big_table WHERE wide_col = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
你需要一个关于wide_col
的索引,而不是wide_col_checksum
。
SQL Server将索引存储为B树。正如@MartinSmith建议的那样,减少索引中列的大小确实会减少内存和磁盘空间。
SQL Server不会自动开始使用校验和/哈希索引。查询需要使用sql server的散列列来考虑使用索引。所以我不知道如何实现更改查询的目标。然而,这是一个有趣的问题,可能是SQL Server的一个很好的功能请求。
我有一个解决方案,这是一项艰巨的任务!
您可以重命名表,然后使用表的名称创建一个视图,并在视图内部执行操作。
我们的想法是用视图捕获对表的调用,如果在wide_col
上没有直接过滤器或使用ix_checksum
索引对应的记录,则在视图内部返回所有记录。
我使用sys.dm_exec_requests
和sys.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
请享用
在大多数排序规则中,这两个查询可以提供不同的结果,因为'A'='a'
,但CHECKSUM('A')
不等于CHECKSUM('a')
。即使在CS_AS或BIN排序规则上,尾随空格也可能是个问题。这就是为什么SQL Server无法自动使用这样的索引。