【问题标题】:SQL WHERE IN () Performance OptimizationSQL WHERE IN() 性能优化
【发布时间】:2020-10-01 06:50:09
【问题描述】:

我检查了几个问题是否重复,但我找不到一个。我正在处理三个表,第一个“articles”,第二个“tags”,第三个“article_tags”,其中包含两个外键“articleid”和“tagid”。 “article_tags”表将共享相同标签的文章关联在一起。

我的SQL查询如下:

'Getting 4 tag ids from table article_tags
MyCommand = New SqlCommand("SELECT TOP (4) at.tagid FROM articles a, article_tags at WHERE at.articleid=a.id AND at.articleid=@id", myconnection)
MyCommand.Parameters.Add("@id", SqlDbType.Int).Value = intID
Dim daQuery = New SqlDataAdapter(MyCommand)
Dim dtArticleTags = New DataTable
daQuery.Fill(dtArticleTags)
Dim strTagIds As String = ""
If dtArticleTags.Rows.Count > 0 Then
    'Save all tags id's in a string "strTags"
    '--------------------------------------------- 
    Dim i As Integer = 0
    For Each myrow As DataRow In dtArticleTags.Rows
        'Store the Tag Ids in a string
        strTagIds += myrow.Item("tagid").ToString
        If i <> dtArticleTags.Rows.Count - 1 Then
            strTagIds += ","
        End If
        i += 1
    Next
    '--------------------------------------------- 
    'Getting 5 related articles sharing the same tags (Note: I know that strTagIds is not parametrized but this can never be inputted by a user)
    MyCommand = New SqlCommand("SELECT TOP(5) at.articleid FROM article_tags at, articles a WHERE a.id=at.articleid AND a.publish_flag=1 AND SYSDATETIME() > DATEADD(mi, DATEDIFF(mi, GETUTCDATE(), SYSDATETIME()), a.publish_date) AND at.tagid IN (" & strTagIds & ") AND at.articleid<>@id Group by at.articleid, a.publish_date ORDER BY a.publish_date DESC", myconnection)
    MyCommand.Parameters.Add("@id", SqlDbType.Int).Value = intID
    daRelated = New SqlDataAdapter(MyCommand)
    daRelated.Fill(dsArticle, "related")
End If

我觉得上面的查询需要时间来加载,尤其是“article_tags”表越来越大。我正在使用 SQL Express Edition,并且表已编入索引,我想知道这是否可以以更好的方式完成以提高性能。

附上执行计划:

按照@SuperPoney 的建议运行以下查询,返回相同的结果,以下是执行计划:

WITH top_art AS
(
    SELECT TOP (4) at.tagid
    FROM articles a, article_tags at
    WHERE at.articleid=a.id
    AND at.articleid=@id
)
SELECT TOP(5) at.articleid
FROM article_tags at, articles a, top_art
WHERE a.id=at.articleid
AND a.publish_flag=1
AND SYSDATETIME() > DATEADD(mi, DATEDIFF(mi, GETUTCDATE(), SYSDATETIME()), a.publish_date)
AND at.tagid=top_art.tag_id
AND at.articleid<>@id
Group by at.articleid, a.publish_date
ORDER BY a.publish_date DESC

【问题讨论】:

  • Re "我觉得..." - 在 SSMS 中运行您的查询,记录它的执行计划,显示它(有一些网站可以粘贴它)然后我们可以说一些有意义的东西: )
  • 查询性能受索引、统计信息和执行计划的影响。查询优化器足够智能,可以简化查询并为具有不同语法但逻辑行为相同的查询生成相同执行计划。
  • 这个条款试图达到什么目的 - SYSDATETIME() &gt; DATEADD(mi, DATEDIFF(mi, GETUTCDATE(), SYSDATETIME()), a.publish_date)?
  • 您要寻找的实际结果是什么?在我看来,您似乎是在尝试在代码中进行连接,而不是正确使用 sql。
  • 执行计划已添加到问题中。确保服务器上的日期大于 publish_date。尝试检索共享相同标签的最新文章

标签: sql sql-server vb.net performance database-performance


【解决方案1】:

您可以在一个查询中获得所需的一切:

SELECT  TOP (5) a.ID
FROM    article AS a
WHERE   a.publish_flag = 1 
AND     a.publish_date <  DATEADD(mi, DATEDIFF(mi, GETUTCDATE(), SYSDATETIME()), SYSDATETIME())
AND     a.Id <> @ID 
AND     EXISTS 
        (   SELECT  1
            FROM    article_tags AS at
            WHERE   at.ArticleID = a.ID
            AND     EXISTS  
                    (   SELECT  1 
                        FROM    article_tags AS at2
                        WHERE   at2.ArticleID = @ID
                        AND     at2.TagID = at.TagID
                    )
        )
ORDER BY a.publish_date DESC;
                    

我假设您最初出于性能原因使用TOP 4 作为标签的任意限制,因为没有排序。所以省略了这一点。我还更改了您的谓词:

SYSDATETIME() > DATEADD(mi, DATEDIFF(mi, GETUTCDATE(), SYSDATETIME()), a.publish_date)

a.publish_date <  DATEADD(mi, DATEDIFF(mi, GETUTCDATE(), SYSDATETIME()), SYSDATETIME())

含义相同,但是通过在运行时常量SYSDATETIME()UTCDATETIME() 上调用DATEADD/DATEDIFF 函数,这意味着该计算只进行一次,而不是对每个a.publish_date 进行一次这意味着publish_date 上的任何索引现在都可以使用。

我所做的另一项更改是使用EXISTS 而不是JOIN 将文章链接到标签。这将避免重复,但是使用 GROUP BY 删除重复同样简单,例如

SELECT  TOP (5) a.ID
FROM    article AS a
        INNER JOIN article_tags AS at
            ON at.ArticleID = a.ID
WHERE   a.publish_flag = 1 
AND     a.publish_date <  DATEADD(mi, DATEDIFF(mi, GETUTCDATE(), SYSDATETIME()), SYSDATETIME())
AND     a.Id <> @ID 
AND     EXISTS  
        (   SELECT  1 
            FROM    article_tags AS at2
            WHERE   at2.ArticleID = @ID
            AND     at2.TagID = at.TagID
        )
GROUP BY a.ID, a.publish_date
ORDER BY a.publish_date DESC;

还有一些与上述答案没有直接关系的旁注,但仍然值得一提。

  1. 您使用的隐式连接语法在 28 年前被 ANSI 92 显式连接语法取代。有are plenty of good reasons 切换到“新”语法,所以我建议你这样做。
  2. 参数化查询不仅仅是 SQL 注入攻击(包括但不限于类型安全和查询计划缓存),所以仅仅因为您的输入不是来自用户并不意味着您不应该使用参数化查询.
  3. 我强烈建议不要重复使用您的 SqlClient 对象(SqlConnection、SqlCommand),为每次使用创建一个新对象,并在完成后正确处理它。

【讨论】:

  • 感谢您的回答。那么如何判断查询是否执行得更好呢?仅仅通过运行执行计划?关于 SqlClient 对象,它们被放置在应该自动处理的 USING 语句中。感谢您的支持。
  • 我认为这是一个很好的答案(并且对此表示赞同)。此查询的最大优点是它可以使用索引进行查找,而不是全表/索引扫描。对于规模,您需要确保您有适当的索引,例如,至少在文章表中的 publish_date 和在 article_tags 中的 articleid。然后 SQL Server 可以直接跳转到表中的相关位置。
  • 之所以使用TOP(4)是因为每篇文章最多可以有4个标签。
  • 执行计划会让您对性能有所了解,但无法替代简单的基准测试。如果您想比较两个查询,请使用不同的参数多次运行它们,看看哪一个始终表现更好。
  • @GarethD 所犯的错误是我使用了两个查询和一些代码来实现我所需要的,而它可以在一个查询中实现。另外,我使用的查询是检查所有文章的 SYSDATETIME() 而不是只检查一次 publish_date,对吗?
【解决方案2】:

我的第一个建议是“让数据库完成工作”。 你能在你的数据库中解析这两个查询的查询计划吗?

也许这会回答你的问题。

查询 #1

SELECT TOP(5) at.articleid
FROM article_tags at, articles a
WHERE a.id=at.articleid
AND a.publish_flag=1
AND SYSDATETIME() > DATEADD(mi, DATEDIFF(mi, GETUTCDATE(), SYSDATETIME()), a.publish_date)
AND at.tagid IN (
    SELECT TOP (4) at.tagid
    FROM articles a, article_tags at
    WHERE at.articleid=a.id
    AND at.articleid=@id
)
AND at.articleid<>@id
Group by at.articleid, a.publish_date
ORDER BY a.publish_date DESC

查询 #2

WITH top_art AS
(
    SELECT TOP (4) at.tagid
    FROM articles a, article_tags at
    WHERE at.articleid=a.id
    AND at.articleid=@id
)
SELECT TOP(5) at.articleid
FROM article_tags at, articles a, top_art
WHERE a.id=at.articleid
AND a.publish_flag=1
AND SYSDATETIME() > DATEADD(mi, DATEDIFF(mi, GETUTCDATE(), SYSDATETIME()), a.publish_date)
AND at.tagid=top_art.tagid
AND at.articleid<>@id
Group by at.articleid, a.publish_date
ORDER BY a.publish_date DESC

【讨论】:

  • 我认为您应该将 top_art.tag_id 修复为 top_art.tagid。我执行了查询,它返回了相同的结果。我将执行计划添加到问题中。
  • 我已经修正了错字:)
猜你喜欢
  • 2018-02-18
  • 2011-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多