SQL Server从大表中选择慢

时间:2009-12-02 19:25:15

标签: sql sql-server tsql events

我有一张包含大约2千万条记录的表格。

结构就像:

EventId UNIQUEIDENTIFIER
SourceUserId UNIQUEIDENTIFIER
DestinationUserId UNIQUEIDENTIFIER
CreatedAt DATETIME
TypeId INT
MetaId INT

表每天收到大约10万条记录。

我在除MetaId之外的每一列上都有索引,因为它没有在'where'子句中使用

问题是当我想要拿起例如。所需SourceUserId的最新100条记录

查询有时需要4分钟才能执行,这是不可接受的。

例如

SELECT TOP 100 * FROM Events WITH (NOLOCK)
WHERE SourceUserId = '15b534b17-5a5a-415a-9fc0-7565199c3461'
AND 
(
 TypeId IN (2, 3, 4)
    OR 
 (TypeId = 60 AND SrcMemberId != DstMemberId)
)
ORDER BY CreatedAt DESC

我无法进行分区等操作,因为我使用的是标准版的SQL Server,而且Enterprise太贵了。

我也认为表格很小而且很慢。

我认为问题在于ORDER BY子句,因为db必须经历更大的数据集。

有任何想法如何让它更快?

也许关系数据库对于那种数据不是一个好主意。

始终通过CreatedAt DESC

订购数据

感谢您的阅读。

PabloX

9 个答案:

答案 0 :(得分:15)

您可能希望为此类查询创建复合索引 - 当查询运行缓慢时,很可能选择扫描CreatedAt列上的索引并对SourceUserId值执行残差过滤器,现实你想要发生的是直接跳转到正确订购的给定SourceUserId的所有记录 - 为此,你需要主要在SourceUserId上创建一个复合索引(执行相等性检查),然后在CreateAt上创建(以保留给定SourceUserId值内的顺序)。您可能还想尝试添加TypeId,具体取决于此列的选择性。

因此,最有可能提供最佳可重复性能的2(尝试它们并进行比较)将是:

  1. 索引(SourceUserId,CreatedAt)
  2. 索引(SourceUserId,TypeId,CreatedAt)
  3. 与往常一样,在确定索引的方式/内容/位置时还需要考虑许多其他注意事项,正如Remus在单独的答案中讨论的那样,一个重要的考虑因素是覆盖查询与保持查找。此外,您还需要考虑写入卷,possible fragmentation impact (if any),单例查找与大型顺序扫描等等。

答案 1 :(得分:6)

  

我在每列上都有索引,除了   MetaId

非覆盖索引可能会达到'tipping point',查询将恢复为表扫描。只是在每个列上添加索引,因为它在where子句中使用并不等于良好的索引设计。以您的查询为例,一个好的100%覆盖索引将是:

INDEX ON (SourceUserId , CreatedAt) INCLUDE (TypeId, SrcMemberId, DstMemberId)

以下索引也很有用,尽管它仍然会导致查找:

INDEX ON (SourceUserId , CreatedAt) INCLUDE (TypeId)

最后一个索引没有任何包含的列可以帮助,但是可能会被忽略(取决于列统计和基数估计):

INDEX ON (SourceUserId , CreatedAt)

但是,对于您的查询,SourceUSerId和CreatedAt上的单独索引基本上没用。

请参阅Index Design Basics

答案 2 :(得分:5)

该表具有基于GUID值构建的索引,这表明可能会影响性能的一系列问题:

  • 高索引碎片:,因为新的GUID是随机生成的,索引无法按顺序组织它们,节点的分布也不均匀。
  • 页面拆分次数过多: GUID的大小(16个字节)会导致索引中出现很多页面拆分,因为新值不会超出剩余空间的可能性可在页面中找到。
  • 慢值比较:比较两个GUID是一个相对较慢的操作,因为必须匹配所有33个字符。

这里有一些关于如何调查和解决这些问题的资源:

答案 3 :(得分:1)

我建议在2个sep var表中获取数据

INSERT INTO @Table1
SELECT * FROM Events WITH (NOLOCK)
WHERE SourceUserId = '15b534b17-5a5a-415a-9fc0-7565199c3461'
AND 
(
 TypeId IN (2, 3, 4)
)
INSERT INTO @Table2
SELECT * FROM Events WITH (NOLOCK)
WHERE SourceUserId = '15b534b17-5a5a-415a-9fc0-7565199c3461'
AND 
(
 (TypeId = 60 AND SrcMemberId != DstMemberId)
)

然后从选择,有序和顶部应用联合。限制来自get go的数据。

答案 4 :(得分:1)

我建议使用UNION:

SELECT TOP 100 x.*
  FROM (SELECT a.*
          FROM EVENTS a
         WHERE a.typeid IN (2, 3, 4)
        UNION ALL
        SELECT b.*
          FROM EVENTS b
         WHERE b.typeid = 60 
           AND b.srcmemberid != b.dstmemberid) x
 WHERE x.sourceuserid = '15b534b17-5a5a-415a-9fc0-7565199c3461'

答案 5 :(得分:1)

我们通过移动到事件表的BIGINT IDENTITY键实现了轻微的收益;通过将其用作群集主键,我们可以作弊并将其用于日期排序。

答案 6 :(得分:0)

我会确保CreatedAt正确编入索引

答案 7 :(得分:0)

您可以使用UNION将查询拆分为两个以避免OR(这可能导致您的索引不被使用),例如

   SElect * FROM(
 SELECT TOP 100 * FROM Events WITH (NOLOCK)
WHERE SourceUserId = '15b534b17-5a5a-415a-9fc0-7565199c3461'
AND TypeId IN (2, 3, 4)
UNION  SELECT TOP 100 * FROM Events WITH (NOLOCK)
WHERE SourceUserId = '15b534b17-5a5a-415a-9fc0-7565199c3461' 
 AND TypeId = 60 AND SrcMemberId != DstMemberId
)
ORDER BY CreatedAt DESC

另外,检查uniqueidentifier索引是否不是CLUSTERED。

答案 8 :(得分:0)

如果每天添加100K记录,则应检查索引碎片。 并相应地重建或重组它。 更多信息 : SQLauthority