如何加速涉及count的SQL Server查询(distinct())

时间:2010-01-12 20:56:26

标签: sql sql-server tsql sql-server-2008 query-optimization

我有一个看似简单的SQL Server查询,这比我预期的要长很多。

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上有一个索引。

'listens'表中有超过700万行,其中17,000行与相关网址相匹配,查询结果为5,500。

在一台相当空闲的双核AMD Opteron 2GHz和1GB RAM上运行SQL Server 2008上的查询需要1分钟以上。

有关如何缩短执行时间的任何想法?理想情况下应该不到1秒!

7 个答案:

答案 0 :(得分:5)

在网址上创建一个涵盖GUID

的索引
CREATE INDEX ix_listens_url__guid ON listens (url) INCLUDE (guid)

当将url作为标识符处理时,最好存储和索引URL哈希而不是整个URL

答案 1 :(得分:2)

无论如何,扫描索引都会花费很长时间 你需要做的是缩短指数 你可以做的是有一个整数列,其中计算和存储网址的校验和。 这样你的索引就会变窄,数量会很快。

请注意,校验和不是唯一的,但它足够独特。 这是一个完整的代码示例,说明如何执行此操作。我已经包含了两列的校验和,但它可能只需要一个。您还可以自行计算插入或更新的校验和,并删除触发器。

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

答案 2 :(得分:2)

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

注意到:

  1. guid是VARCHAR(64)**而不是真正的16byte uniqueidentifier
  2. url是varchar(900),你有700万行。
  3. 我的建议:

    1. 为表格创建新字段。 Column = URLHash AS UNIQUEIDENTIFIER 创造新纪录。 URLHash = CONVERT( UNIQUEIDENTIFIER, HASHBYTES('MD5', url) )
    2. 在URLHash上构建索引
    3. 然后在你的查询中: SELECT COUNT(DISTINCT(guid)) FROM listens WHERE URLHash = CONVERT( UNIQUEIDENTIFIER, HASHBYTES( 'MD5', 'http://www.sample.com/' ) )

      这将为您提供一种非常快速的独特搜索特定网址的方法,同时保持非常小的索引大小。

      如果您需要进一步优化,您可能希望在guid上执行相同的哈希。在16byte uniqueidentifier上执行distinct更快比varchar(64)更快。


      以上假设是您没有在监听表中添加大量新行;也就是说,新的记录率并不那么重。原因是MD5算法虽然提供了完美的色散;是出了名的慢。如果要以每秒数千的速度添加新记录;然后在创建记录时计算MD5哈希会降低服务器的速度(除非你有一个非常快的服务器)。另一种方法是实现您自己的FNV1a散列算法版本,该算法不是内置的。与MD5相比,FNV1a快得多,但提供了非常好的色散/低截留率。

      希望以上帮助将来遇到这类问题的人。

答案 3 :(得分:0)

您的GUID专栏本质上会比bigint占用更多的空间(16 bytes)。您是否可以使用自动递增的数字列替换GUID列,或者如果失败,则引入类型为bigint / int的新列,该列会针对{{的每个新值递增1}}列(然后您可以使用GUID确保全局唯一性,并使用GUID t进行索引编制)?

从上面的链接:

  

16字节,uniqueidentifier数据   类型相对较大   其他数据类型,如4字节   整数。这意味着建立了索引   使用uniqueidentifier键可能是   相对慢于实施   使用int键的索引。

您使用bigint/in作为guid列而不是varchar是否有任何特殊原因?

答案 4 :(得分: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)告诉确切的SQL Serever在执行查询时必须扫描哪个索引(当然,按url字段索引)。另一种方法 - 由guid简单地删除索引并仅由url留下索引。查看here以获取有关提示的更多信息。特别是对于像select ... from listens with (index(index_name_for_url_field) )

这样的结构

3)验证listens表上的索引状态并更新index statistics

答案 5 :(得分:0)

我敢打赌,如果你的机器内存超过1GB,它的性能会更好(我所遇到的所有DBA在生产SQL服务器中都至少有4GB。)

我不知道这是否重要,但如果你做了

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

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

答案 6 :(得分:0)

你最好的计划是寻求获得17k候选网址的范围,并且计数明显依赖于保证的输入顺序,因此它不必排序。能够满足这两个要求的正确数据结构是(url, guid)

的索引
CREATE INDEX idxListensURLGuid on listens(url, guid);

您已经对所使用的密钥的广泛性有了大量的反馈,您可以明确地寻求改进它们,如果可以的话,还可以增加1Gb的RAM。

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