2010-01-12 56 views
4

我有一个看似简单的SQL Server查询,花费的时间比我预期的要长很多。如何加快SQL Server查询涉及计数(distinct())

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 
SELECT COUNT(DISTINCT(guid)) FROM listens WHERE url='http://www.sample.com/' 

'的GUID' 为varchar(64)NULL

'URL' 为varchar(900)NULL

上有GUID和URL的索引。

“侦听”表中有700多万行,其中17,000与所讨论的URL相匹配,查询结果为5,500。

在一个相当闲置的双核AMD Opteron 2GHz 1GB RAM上运行SQL Server 2008上的这个查询需要花费1分多钟。

任何想法如何让执行时间缩短?理想情况下,它应该在1秒以内!

+2

'有一个关于guid和url的索引。'这是两个单独的索引还是一个组合索引? – 2010-01-12 21:00:22

+0

2个单独的索引 – 2010-01-12 21:09:22

回答

0

您的GUID列本质上会比bigint更耗费人力,因为它占用更多空间(16 bytes)。您能否使用自动递增的数字列来替换GUID列,否则会引入一个类型为bigint/int的新列,该列将为GUID列的每个新值递增(然后可以使用您的GUID确保全局唯一性,和用于索引目的的bigint/in)?

从上面的链接:

在16个字节,比其它 数据类型,如4个字节的整数 唯一标识符数据 类型是比较大的。这意味着使用uniqueidentifier密钥构建的索引 可能是 相对比使用int键实现 索引要慢。

是否有什么特别的原因,你为什么使用varchar作为你的guid列而不是uniqueidentifier

+0

guid是从外部来源提供的值。目前它确实看起来像一个独特的标识符,但这不能保证,所以它需要是一个字符串。我可能会创建另一个将int映射到guid的表,但这会导致插入'listen'更加昂贵,并且我需要快速插入。 – 2010-01-12 22:29:50

5

创建于网址索引这将覆盖GUID

CREATE INDEX ix_listens_url__guid ON listens (url) INCLUDE (guid) 

当网址作为标识符处理,它是更好的存储和索引URL哈希而不是整个URL

+0

请注意,创建如此宽的索引并不是一个好主意。他们只占用空间,只在少数情况下才有用。 我同意网址散列,虽然我更喜欢校验和(它更窄,而且速度更快),正如我在回答中所说的那样。 – 2010-01-12 21:49:11

+0

通过URL列索引就足够了,还有一个需要:如何告诉超级智能的MS SQL Server如何构建正确的查询计划:) – ThinkJet 2010-01-13 12:48:04

+0

查询计划最不用担心。这就是IO问题。巨大的索引会导致巨大的IO。 – 2010-01-13 16:21:25

2

无论发生什么事都会花费很长时间的扫描索引。
你需要做的是缩短索引。
你可以做的是有一个整数列,其中URL的校验和被计算和存储。 这样你的索引将会变窄并且数量会很快。

请注意,校验和不是唯一的,但它是独一无二的。 这是一个完整的代码示例。我已经包括两列的校验和,但它可能只需要一个。您也可以自行计算插入或更新的校验和并删除触发器。

CREATE TABLE MyTable 
(
    ID INT IDENTITY(1,1) PRIMARY KEY, 
    [Guid] varchar(64), 
    Url varchar(900), 
    GuidChecksum int, 
    UrlChecksum int 
) 
GO 

CREATE TRIGGER trgMyTableCheckSumCalculation ON MyTable 
FOR UPDATE, INSERT 
as 
UPDATE t1 
SET GuidChecksum = checksum(I.[Guid]), 
     UrlChecksum = checksum(I.Url) 
FROM MyTable t1 
     join inserted I on t1.ID = I.ID 

GO 
CREATE NONCLUSTERED INDEX NCI_MyTable_GuidChecksum ON MyTable(GuidChecksum) 
CREATE NONCLUSTERED INDEX NCI_MyTable_UrlChecksum ON MyTable(UrlChecksum) 

INSERT INTO MyTable([Guid], Url) 
select NEWID(), 'my url 1' union all 
select NEWID(), 'my url 2' union all 
select null, 'my url 3' union all 
select null, 'my url 4' 

SELECT * 
FROM MyTable 

SELECT COUNT(GuidChecksum) 
FROM MyTable 
WHERE Url = 'my url 3' 
GO 

DROP TABLE MyTable 
+0

如果您想添加一个示例,说明在这种情况下选择的样子。 (其中url_crc = crc('url')和url ='url')或类似的东西。 – 2010-01-13 08:54:48

+0

散列(这里称为“校验和”)不是一个答案,因为它不是唯一的,“url”字段的实际值必须针对给定值进行测试。因此SQL Server必须读取字段的实际值。 – ThinkJet 2010-01-13 12:36:18

+0

-1至少'select count()...'查询是错误的:1)真正的不同的guid必须被计数,而不是非唯一的校验和2)UrlChecksum必须添加在WHERE子句中,服务器没有任何理由使用UrlChecksum的指数 – ThinkJet 2010-01-13 12:40:52

0

一些提示...

1)重构您的查询,例如使用with条款等

 
    with url_entries as ( 
     select guid 
     from listens 
     where url='http://www.sample.com/' 
    ) 
    select count(distinct(enries.guid)) as distinct_guid_count 
    from url_entries entries 

2)告诉该索引必须在执行查询(当然,指数url场)进行扫描精确SQL Serever。另一种方法 - 简单的下降指数​​和离开指数url单独。有关提示的更多信息,请参阅here。特别是对于像select ... from listens with (index(index_name_for_url_field))

3)验证​​表索引状态和更新index statistics

0

我敢打赌,如果你有这将有更好的表现这台机器,1GB的内存(所有DBA的我见过期望至少4GB的生产SQL服务器。)

我不知道,如果这但如果你这样做

SELECT DISTINCT(guid) FROM listens WHERE url='http://www.sample.com/' 

会不会@rowcount包含你想要的结果?

0

你最好的可能的计划是寻找一个范围寻求获得17k候选URL和计数不同,以依靠保证的输入顺序,因此它不必排序。合适的数据结构,可以同时满足这些要求是对(url, guid)指数:

CREATE INDEX idxListensURLGuid on listens(url, guid); 

你已经有足够的反馈所使用的密钥的wideness,您可以definetely寻求改善他们,也增加如果可以的话,小心1Gb的RAM。

如果可以在SQL 2008 EE上进行部署,那么请确保您为page compression打开了这样一个高度重复且宽泛的索引。由于减少了IO,它将在性能方面创造奇迹。

2

我知道这篇文章有点晚了。我正在寻找另一个优化问题。

注意到:

  1. GUID是VARCHAR(64)**,而不是真正的唯一标识符
  2. URL一个16字节为varchar(900),你有7个百万行吧。

我的建议:

  1. 为表创建一个新的领域。 Column = URLHash AS UNIQUEIDENTIFIER 创建新记录。 URLHash = CONVERT(UNIQUEIDENTIFIER, HASHBYTES('MD5', url))
  2. 建立在URLHash

那么指数在您的查询: SELECT COUNT(DISTINCT(guid)) FROM listens WHERE URLHash = CONVERT(UNIQUEIDENTIFIER, HASHBYTES('MD5', 'http://www.sample.com/'))

这会给你的唯一追求特定的URL,同时保持一个非常小的索引大小非常快的方法。

如果您需要进一步优化,您可能希望对guid执行相同的散列操作。在16byte uniqueidentifier上执行一个独立的比varchar(64)更快。


上面的假设是你没有将ALOT的新行添加到listen表中;即新记录率并不那么重。原因在于MD5算法虽然提供了完美的分散性;是臭名昭着的缓慢。如果您以每秒数千的数量添加新记录,那么计算创建记录时的MD5哈希值可能会降低您的服务器速度(除非您的服务器速度非常快)。另一种方法是实现您自己的FNV1a哈希算法,该算法不是内置的。与MD5相比,FNV1a快很多,但仍能提供非常好的分散/低碰撞率。

希望以上内容有助于未来遇到这类问题的人。