【问题标题】:SQL caching strategySQL缓存策略
【发布时间】:2011-01-18 20:24:40
【问题描述】:

我正在开发一个交互式联系人搜索页面(当您键入或选择条件时,联系人会通过 ajax 返回)。我希望这个页面反应灵敏。

有一套复杂的规则来确定给定联系人可以看到哪些联系人记录;这些规则汇总到用户定义的函数DirectoryContactsByContact(@ContactID) 中。我已经对这个函数进行了相当大的优化,但它仍然有点贵(执行 1-2 秒),所以为了提高性能,我正在考虑这样的事情:

  • 页面加载时,将此用户的 DirectoryContactsByContact 缓存为 SQL 表,例如cache_DirectoryContactsByContact_1
  • 对缓存表执行搜索(每次检查以确保它存在)
  • 一段时间后(比如 30 分钟)终止缓存

如果在此期间数据过时也没关系,所以我不担心失效。

临时表不会在请求之间持续存在,因此我似乎需要将缓存表创建为永久表;但是我需要自己负责清理旧的缓存,乍一看这看起来并不简单。

SQL Server 中是否有任何机制可以使这更容易?关于替代方法的任何建议?

【问题讨论】:

  • FWIW 我不想将数据缓存在 .NET 的内存中,因为 (a) 有很多数据,并且 (b) 搜索涉及全文索引和连接以及其他SQL 做得很好。所以我肯定想把它缓存成一个 SQL 表。

标签: sql sql-server sql-server-2005 optimization caching


【解决方案1】:

每当页面加载时,将函数的结果插入到永久表中,比如 SearchResults。该表将包含如下字段:

  • 搜索联系人 ID
  • 目录联系人ID
  • 创建日期

您将针对此表进行搜索。然后 - 每天或任何时候 - 您将有一个过程来检查此表并删除一天多以前的任何内容。

【讨论】:

  • 这不是一个坏主意。但是,如果我要麻烦滚动自己的缓存基础架构,那么(在我的情况下)缓存所有正在查询的数据而不仅仅是 ContactID 是有意义的,这样我就不必做用户打字时的任何加入或任何工作。
【解决方案2】:

我不想将数据缓存在 .NET 中的内存,因为 (a) 有一个 大量数据,以及 (b) 搜索 涉及全文索引和连接 以及其他 SQL 擅长的东西。

这是否意味着搜索的数据是“很多”,或者搜索结果是“很多”? DirectoryContactsByContact(@ContactID)的输出有多大?我的假设是这是一个小结果集,小到足以在 ASP 端有用。如果这是真的,那么您应该在 ASP 中缓存特定@ContactID 的搜索结果,并为相同的重复@ContactID 重新使用该缓存结果,直到它从缓存中过期,然后重新创建它。

我不喜欢将结果缓存为 SQL 中的表。这种方法将读取转换为写入,从而进一步减慢第一次命中。它提供过时的数据,需要清理。但最重要的是,根据我的经验,它总是避免了由于数据模型架构设计不当而导致查询无效的真正问题。

您对DirectoryContactsByContact(@ContactID) 的响应时间不能进一步缩短有多大信心?瓶颈在哪里?你是怎么测量的?您是否考虑过可以进行哪些架构更改以更快地提供此结果?

【讨论】:

  • DirectoryContactsByContact(@ContactID) 可能包含超过 200K 行。但是 (b) 是一个更大的问题 - 一旦缩小到允许此联系人查看的内容,仍然需要执行一些复杂的过滤,以便 SQL 更适合。
  • DirectoryContactsByContact(@ContactID) 执行的业务规则太复杂,无法在这个空间中讨论;可以说发生了很多事情就足够了。无论如何,查询缓存表和从冷启动(15 ms v 600 ms)查询函数之间大约有 40 倍的差异,我怀疑我可以通过调整查询来挤出这种性能。
  • 我明白了。我在你原帖的字里行间读到了,但我不得不问一下。你最好的选择是像 Sylvia 建议的那样。
【解决方案3】:

我最终创建了一个基本的通用框架,用于将 SQL 函数或视图的结果缓存到表中。

    Public Sub CreateCacheTable(ByVal SourceView As String, ByVal FieldList As String)
        Dim CacheTable As String = GetCacheTableName(SourceView)
        If Not TableExists(CacheTable) Then
            Dim Sql As String = " Select ~FieldList~ Into ~CacheTable~ From ~SourceView~ ". _
                Replace("~CacheTable~", CacheTable). _
                Replace("~FieldList~", FieldList). _
                Replace("~SourceView~", SourceView)
            ExecuteNonQuery(cs, CommandType.Text, Sql)
        End If
    End Sub

    Public Function GetCacheTableName(ByVal SourceView As String)
        Dim Result As String = "_c_~SourceView~". _
            Replace("~SourceView~", SourceView). _
            Replace(".", "_"). _
            Replace(",", "_"). _
            Replace("[", ""). _
            Replace("]", ""). _
            Replace("(", ""). _
            Replace(")", "")
        Return Result
    End Function

    Public Sub CleanupCacheTables()
        ExecuteNonQuery(cs, CommandType.StoredProcedure, "CleanupCacheTables") 
    End Sub

当页面加载时,我会这样做:

        CleanupCacheTables()
        CreateCacheTable(SourceView, FieldList)

例如,如果 SourceView 是 DirectoryContactsByContact(123),则会创建一个名为 _c_DirectoryContactsByContact_123 的表。

这是CleanupCacheTables 的 SQL:

Create Procedure CleanupCacheTables as
    /* Finds all tables starting with _c_ that were created more than 30 minutes ago and drops them */
    Declare @TableName nvarchar(255)
    Declare CacheTableCursor Cursor for
        Select 
            TableName=name
        From SYS.OBJECTS
        Where Type_Desc = 'USER_TABLE'
        And Left(name,3)=  '_c_'
        And DateDiff(minute, create_date, GetDate())>30
    Open CacheTableCursor
    Fetch Next from CacheTableCursor into @TableName
    While @@FETCH_STATUS = 0 Begin
        Exec ('Drop Table ' + @TableName)
        Fetch Next from CacheTableCursor into @TableName
    End -- While
    Close CacheTableCursor
    Deallocate CacheTableCursor
Go

这是粗略的:没有失效,它可能不会扩展到大量并发用户和/或非常大的数据集。尽管如此,在我的情况下,当用户键入或选择搜索条件时,它会产生近乎即时的结果,而且开销很小。

【讨论】:

    猜你喜欢
    • 2010-10-06
    • 1970-01-01
    • 2012-09-05
    • 2020-08-03
    • 2011-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多