0

我正在开发一个越来越受欢迎的移动网站,这导致一些关键数据库表的增长——我们在访问这些表时开始看到一些性能问题。我们不是数据库专家(现阶段也没有钱聘请任何人),我们正在努力了解导致性能问题的原因。我们的表不是那么大,所以 SQL Server 应该能够很好地处理它们,并且我们已经完成了我们在优化查询方面所做的一切。所以这是(伪)表结构:

[user] (approx. 40,000 rows, 37 cols):

id               INT (pk)
content_group_id INT (fk)
[username]       VARCHAR(20)
...

[content_group] (approx. 200,000 rows, 5 cols):

id    INT (pk)
title VARCHAR(20)
...

[content] (approx. 1,000,000 rows, 12 cols):

id                  INT (pk)
content_group_id    INT (fk)
content_type_id     INT (fk)
content_sub_type_id INT (fk)
...

[content_type] (2 rows, 3 cols)

id INT (pk)
...

[content_sub_type] (8 rows, 3 cols)
id              INT (pk)
content_type_id INT (fk)
...

我们预计这些行数会大幅增长(尤其是 user、content_group 和 content 表)。是的,用户表有很多列——我们已经确定了一些可以移动到其他表中的列。我们还对受影响的表应用了一堆索引,这些索引很有帮助。

最大的性能问题是我们用于搜索用户的存储过程(包括在 content_group_id 字段上连接到内容表)。我们尝试使用各种不同的方法来修改WHEREandAND子句,我们认为我们已经尽可能地完善了它们,但仍然太慢了。

我们尝试的另一件事没有帮助是在用户和内容表上放置一个索引视图。当我们这样做时没有明显的性能提升,所以我们放弃了这个想法,因为拥有视图层固有的额外复杂性。

那么,我们有哪些选择呢?我们可以想到一些,但都有优点和缺点:

表结构的非规范化

在用户表和内容表之间添加多个直接外键约束 - 因此每个内容子类型的内容表会有不同的外键。

优点:

  • 通过使用其主键加入内容表将更加优化。

缺点:

  • 我们现有的存储过程和网站代码会有很多变化。
  • 维护多达 8 个额外的外键(更实际地,我们将只使用其中的 2 个)不会像当前的单个键那样容易。

表结构的更多非规范化

只需将我们需要的字段从内容表中直接复制到用户表中即可。

优点:

  • 不再连接内容表 - 这大大减少了 SQL 必须做的工作。

缺点

  • 同上:需要在用户表中维护的额外字段、对 SQL 和网站代码的更改。

创建中间层索引层

使用 Lucene.NET 之类的东西,我们会在数据库之上放置一个索引层。从理论上讲,这将提高所有搜索的性能,同时减少服务器上的负载。

优点:

  • 这是一个很好的长期解决方案。Lucene 的存在是为了提高搜索引擎的性能。

缺点:

  • 短期内会有更大的开发成本——我们需要尽快解决这个问题。

所以这些是我们想出的东西,在这个阶段,我们认为第二种选择是最好的——我知道非规范化有它的问题,但有时最好牺牲架构纯度以获得性能提升,所以我们准备支付这笔费用。

还有其他方法可能对我们有用吗?我上面概述的方法是否有任何其他优点和/或缺点可能会影响我们的决定?

4

1 回答 1

1

使用 content_sub_type_id 从内容表中查找非聚集索引。接下来是针对内容表的 content_group_id 哈希匹配

此描述表明您的昂贵查询content根据以下字段过滤表content_type

select ...
from content c
join content_type ct on c.content_type_id = ct.id
where ct.<field> = <value>;

这种表格设计,以及您刚才看到的由此产生的问题,实际上是很常见的。问题的出现主要是由于查找表的选择性非常低(content_type有 2 行,因此 content_type_id 在内容中的选择性可能是 50%,非常大)。您可以尝试几种解决方案:

1)content以 content_type_id 作为前导键在聚集索引上组织表。这将允许连接进行范围扫描,并避免为投影完整性进行键/书签查找。作为聚集索引的更改,它会对其他查询产生影响,因此必须仔细测试。content显然,必须使用非聚集约束来强制执行主键。

2) 预读content_type_id值,然后在没有contentand之间连接的情况下制定查询content_type

select ...
from content c
where c.content_type_id = @contentTypeId;

仅当 content_type_id 的选择性很高(许多不同的值,每个值很少)时,这才有效,我怀疑这是您的情况(您可能只有很少的内容类型,每个都有很多条目)。

3) 将 content_Type 非规范化为内容。您提到了非规范化,但是您将内容非规范化为用户的提议对我来说毫无意义。删除content_type表,将 content_type 字段拉入content表本身,并忍受所有非规范化问题。

4) 在物化视图中预加入。你说你已经尝试过了,但我怀疑你尝试了正确的物化视图。您还需要了解只有 Enterprise Edition 自动使用物化视图索引,所有其他版本都需要NOEXPAND提示:

create view vwContentType 
with schemabinding
as 
select content_type_id, content_id
from dbo.content c
join dbo.content_type_id ct on c.content_type_id = ct.content_type_id;

create unique clustered index cdxContentType on vwContentType (content_type_id, content_id);

select ...
from content c
join vwContentType ct with (noexpand)
on ct.content_id = c.content_id
where ct.content_type_id = @contentTypeId;

解决方案 2)、3) 和 4) 大多是学术性的。鉴于 content_type_id 的选择性非常低,您唯一有机会的解决方案是使其成为content. 我没有将分析扩展到content_Sub_type,但只有 8 行我愿意打赌它有同样的问题,这需要将它也推入聚集索引(可能作为第二个前导键)。

于 2011-10-10T17:58:48.057 回答