我们正在使用SQL Server 2005来跟踪大量不断传入的数据(每秒5-15次更新)。我们注意到它已经生产了几个月之后,其中一个表开始花费大量的时间来查询。
该表有3列:
id
- 自动编号(群集)typeUUID
- 在插入发生之前生成的GUID;用于将类型分组在一起typeName
- 类型名称(duh ...)我们运行的其中一个查询在typeName
字段上是不同的:
SELECT DISTINCT [typeName] FROM [types] WITH (nolock);
typeName
字段上有一个非clusted,非唯一的升序索引。该表目前包含大约200M条记录。当我们运行此查询时,查询需要5米58秒才能返回!也许我们不理解索引是如何工作的......但我不认为我们误解了 。
为了进一步测试,我们运行了以下查询:
SELECT DISTINCT [typeName] FROM (SELECT TOP 1000000 [typeName] FROM [types] WITH (nolock)) AS [subtbl]
这个查询大约在10秒后返回,正如我所料,它正在扫描表格。
我们在这里缺少什么?为什么第一个查询需要这么长时间?
编辑:啊,我的道歉,第一个查询返回76条记录,谢谢你的帮助。
跟进:谢谢大家的回答,现在对我来说更有意义(我不知道为什么之前没有...)。没有索引,它正在对200M行进行表扫描,带有索引,它正在对200M行进行索引扫描......
SQL Server确实更喜欢索引,它确实提供了一点性能提升,但没有什么可以激动的。重建索引确实将查询时间缩短到超过3米而不是6米,这是一项改进,但还不够。我只是向老板推荐我们规范表格结构。
再次感谢大家的帮助!!
答案 0 :(得分:9)
你误解了索引。即使它确实使用了索引,它仍然会对200M条目进行索引扫描。这将需要很长时间,加上执行DISTINCT所需的时间(导致排序)并且运行起来是件坏事。在查询中看到DISTINCT总是会引发一个红色标记并导致我仔细检查查询。在这种情况下,您可能有规范化问题吗?
答案 1 :(得分:4)
我怀疑SQL Server甚至会尝试使用索引,它必须做几乎相同的工作量(给定窄表),读取所有200M行,无论它是查看表还是索引。如果typeName
上的索引已群集,则可能会缩短所需的时间,因为在分组之前不需要对其进行排序。
如果您的类型的基数很低,那么维护一个包含不同type
值列表的摘要表怎么样?插入/更新主表时的触发器将检查摘要表并在找到新类型时插入新记录。
答案 2 :(得分:3)
使用DISTINCT
关键字时,SQL Server优化器有一个问题。
解决方案是通过单独分解不同的查询来强制它保持相同的查询计划。
所以我们也查询如下:
SELECT DISTINCT [typeName] FROM [types] WITH (nolock);
并将其分解为以下
SELECT typeName INTO #tempTable1 FROM types WITH (NOLOCK)
SELECT DISTINCT typeName FROM #tempTable1
另一种解决方法是使用GROUP BY
,它会获得不同的优化计划。
答案 3 :(得分:1)
我的第一个想法是统计数据。要查找上次更新:
SELECT
name AS index_name,
STATS_DATE(object_id, index_id) AS statistics_update_date
FROM
sys.indexes
WHERE
object_id = OBJECT_ID('MyTable');
编辑:重建索引时更新统计信息,我看不到维护
我的第二个想法是指数还在吗? TOP查询仍应使用索引。 我刚刚在我的一个表上测试了5700万行并且都使用了索引。
答案 4 :(得分:1)
正如其他人已经指出的那样 - 当你对表执行SELECT DISTINCT(typename)时,无论如何都会进行全表扫描。
所以这实际上是限制需要扫描的行数。
问题是:你需要什么DISTINCT类型名称?你的200M行中有多少是不同的?你是否只有少数(最多几百个)不同的类型名称?
如果是这样 - 你可以有一个单独的表DISTINCT_TYPENAMES或者其他东西并通过执行全表扫描来填充它们,然后在将新行插入主表时,只需要检查它们的类型名是否已经在DISTINCT_TYPENAMES中,如果不,添加它。
这样,你就会有一个单独的小表,只有不同的TypeName条目,这些条目可以快速查询和/或显示。
马克
答案 5 :(得分:1)
循环方法应该使用多次搜索(但会失去一些并行性)。对于与总行数(低基数)相比具有相对较少的不同值的情况,可能值得尝试。
想法来自这个question:
select typeName into #Result from Types where 1=0;
declare @t varchar(100) = (select min(typeName) from Types);
while @t is not null
begin
set @t = (select top 1 typeName from Types where typeName > @t order by typeName);
if (@t is not null)
insert into #Result values (@t);
end
select * from #Result;
看起来还有一些其他方法(特别是递归CTE @Paul White):
答案 6 :(得分:0)
我应该尝试这样的事情:
SELECT typeName FROM [types] WITH (nolock)
group by typeName;
和其他人一样,我会说你需要对该列进行标准化。
答案 7 :(得分:0)
索引可帮助您快速找到一行。但是你要求数据库列出整个表的所有唯一类型。索引对此无能为力。
您可以运行一个运行查询的夜间作业并将其存储在不同的表中。如果您需要最新数据,您可以存储夜间扫描中包含的最后一个ID,并合并结果:
select type
from nightlyscan
union
select distinct type
from verybigtable
where rowid > lastscannedid
另一种选择是将大表规范化为两个表:
talbe1: id, guid, typeid
type table: typeid, typename
如果类型的数量相对较少,这将是非常有益的。
答案 8 :(得分:0)
我可能会遗漏一些东西,但是如果在加载时产生开销以创建具有不同值的视图而不是查询会更有效吗?
如果结果集明显较小,这将给select几乎即时的响应,并且在每次写入时填充它的开销,但考虑到视图的本质可能是微不足道的。
它确实提出了一个问题,与你想要的速度和速度的重要性相比,有多少次写作。
答案 9 :(得分:0)
索引视图可以加快速度。
create view alltypes
with schemabinding as
select typename, count_big(*) as kount
from dbo.types
group by typename
create unique clustered index idx
on alltypes (typename)
在每次更改基表时保持视图最新的工作应该是适度的(取决于您的应用程序,当然 - 我的观点是它不必每次都扫描整个表或做任何像这样疯狂的东西。)
或者你可以制作一个包含所有值的小表:
select distinct typename
into alltypes
from types
alter table alltypes
add primary key (typename)
alter table types add foreign key (typename) references alltypes
外键将确保使用的所有值都显示在父alltypes
表中。问题在于确保alltypes
不包含未在子types
表中使用的值。