背景故事我有一个存储缓存时间的表,目前有大约1米的行。当我用新版本的缓存项更新表时,我需要删除旧的缓存项(约3k项)。关键是这些物品立即被发现但我会优先考虑,因为当客户拿出缓存物品时,我希望他们能够获得最新版本。
但删除仍然是“慢”,需要几秒钟让最终用户等待,有没有办法让这个更快?我正在做一个简单的SQL
DELETE FROM cache where cache_event_id = X
我的问题变成了: 我可以使查询更快(我希望缓存表只增加大小,所以这个问题会变得更糟)? 我应该使删除sql运行自己的线程,并与用户可能旧项目一段时间一起生活吗?
Pr请求表的其余信息。
CREATE TABLE [dbo].[cache](
[cache_id] [int] IDENTITY(1,1) NOT NULL,
[cache_name] [nchar](128) NOT NULL,
[cache_event_id] [int] NOT NULL,
[cache_encounter_id] [int] NOT NULL,
[cache_type_id] [tinyint] NOT NULL,
[cache_creation_date] [datetime] NOT NULL,
[cache_data] [varbinary](max) NOT NULL
) ON [PRIMARY]
所有索引都是由sql server profiler创建的,好像我需要手动删除旧索引 索引1:
CREATE NONCLUSTERED INDEX [_dta_index_cache_6_366624349__K2_K3_K5_K4_7] ON [dbo]. [cache]
(
[cache_name] ASC,
[cache_event_id] ASC,
[cache_type_id] ASC,
[cache_encounter_id] ASC
)
INCLUDE ( [cache_data]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
索引2://可能实际上没有使用
CREATE NONCLUSTERED INDEX [_dta_index_cache_6_366624349__K5_1_2_3_4_6_7] ON [dbo].[cache]
(
[cache_type_id] ASC
)
INCLUDE ( [cache_id],
[cache_name],
[cache_event_id],
[cache_encounter_id],
[cache_creation_date],
[cache_data]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
索引3(我假设这个用户正在删除)
CREATE NONCLUSTERED INDEX [_dta_index_cache_6_366624349__K3] ON [dbo].[cache]
(
[cache_event_id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
通过BulkCopy类
将数据插入表中获取数据(这是最关键的部分)
SqlCommand cmd = new SqlCommand("GetPageCache", connection);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@event_id", EventID); // int
cmd.Parameters.AddWithValue("@encounter_id", EncounterID); // int
cmd.Parameters.AddWithValue("@type_id", (int)CacheType); //int
cmd.Parameters.AddWithValue("@cachename", CacheName); // Required in some cases, but 90% this is just a fallback
答案 0 :(得分:4)
好消息是:如果DELETE语句总是删除大约3000行,那么随着表变大,情况可能不会变得更糟。
表的结构可能会影响DELETE操作的持续时间以及由于锁定而直接影响用户的时间。
索引“帮助”,方便识别~3000个注定行的行定位符。但是,这些行必须位于“整个”表中(并在表中的每个索引中),然后删除。这种情况的一个可能原因是这些3000行分散在不同数据页上的表(和索引)中。
对你来说没有一个通用的答案,但你应该好好看看你的表的组织和索引。可能有一种方法可以更改组织和索引,使得注定的行将位于较少的数据页上,并且DELETE的查询计划将不会执行3000次单独的查找以达到它们。
如果您为[cache]发布CREATE TABLE和CREATE INDEX语句,我可能会提供具体建议而不是概括。
补充说明:
以下是一些想法。
您是否有PRIMARY KEY约束?如果没有,您没有聚集索引,这意味着您的表存储为堆。这并不好,特别是对于经历了大量活动的桌子。虽然我没有详细说明,但我同意下面的Dems。它应该有助于主键(应该是群集的)(cache_event_id,cache_id)。
另一个瓶颈可能是缓存数据本身。您已在三个索引中包含它,因此您将它存储在四个位置!我只是猜测,但你似乎不太可能一次从多行返回cache_data列的查询。因此,您只能将cache_data存储在聚簇索引中(默认情况下,聚簇索引包括所有列)。数据库调优顾问很好地为您提供想法,但完全按照它所说的做并不总是一个好主意。
典型的cache_data列有多大?如果它几乎总是很大(大小超过8K),那么它将导致LOB溢出页面的大量活动。当有很多LOB活动时,我不是工作负载调优的专家,但是可能有一些很好的资源和建议。要考虑的一件事(直到你尝试索引改进并实际查看内存使用,缓存命中等)是考虑允许更多表行适合页面的更改:
重新考虑是否需要该类型 nchar(128)表示cache_name。 (您 可能,但想一想。是吗 总是近128字节的数据?是 使用Unicode必要和 值得额外的空间?如果没有,也许吧 nvarchar(128)或varchar(128)是 好的。)
考虑它是否有用 设置'大值类型 行'选项为ON。默认是 关闭,这可能会导致你 每个数据只有一个表行 页面平均,但没有减少 需要LOB溢出页面。 查看sp_spaceused的结果 或sys.dm_db_partition_stats尝试 评估这个。如果你只有1 或者每页2行,这可能会有所帮助 更改设置。
答案 1 :(得分:2)
如果有很多删除,可能是在日志文件中写了很多东西。如果涉及任何关系,可能需要花费很长时间来确定是否允许删除记录。
我有类似的问题,(但在我的情况下,我需要确保旧的记录不可见) 并最终添加了一个名为hidden的位域。因此,'delete'例程实际上只是一个更新语句,将hidden设置为true,并修改了查找以忽略隐藏记录。
然后我可以在不影响用户的情况下删除后台隐藏的记录。
答案 2 :(得分:1)
让最终用户等待执行缓存清理的删除调用似乎是不必要的。这当然应该是一个后台工作/线程。
或者,您可以使用memcached之类的内容来处理缓存读取和过期。
答案 3 :(得分:1)
这些数据的清理绝对应该是异步完成的(通过预定的sql作业,服务,填充数据的作业等)。如果您担心在有机会删除查询之前返回查询中的旧项目,则可以实现某种类型的版本控制方案,该方案只返回最新项目。
答案 4 :(得分:0)
我认为问题的一部分是设计,但假设我们只想加速删除而不改变其他内容?
确实只在“删除”中使用了“cache_event_id”的索引,但这并不是您可能会想到的方式。使用执行计划运行删除,您将在使用索引后看到它然后也使用主键(假设已聚集)。索引基本上只是用作查找需要删除的主键的捷径。主键(或任何聚簇索引)允许RDBMS物理地知道记录的位置,因此可以删除它们。
此外,删除记录时,所有索引都需要更新。根据你拥有的索引数量以及它们的设置方式,这可以是labourios。
所以我的两个推荐是:
1.确保主键或聚簇索引具有“cache_event_id”作为第一个字段
2.合理化索引的数量,如果可能,这可能涉及重写一些查询