SQL查询根据参数显示出截然不同的性能

时间:2015-12-21 19:23:12

标签: sql-server query-performance

我们有一个存储过程,它根据一个不同场景的输入参数来搜索产品。根据输入参数,搜索涉及从两个到大约十二个不同的表。为了避免不必要的连接,我们将实际的搜索查询构建为动态SQL,并在存储过程中执行它。

在一个最基本的方案中,用户仅通过关键字搜索产品(请参阅下面的查询1 ),这通常只需不到一秒钟。但是,如果他们按关键字和部门(查询2 )进行搜索,则执行时间会超过一分钟,执行计划看起来会有所不同(计划的附加快照正在显示只是不同的部分。)

查询1(快速)

SELECT DISTINCT
    Product.ProductID, Product.Title 
FROM 
    Product 
    INNER JOIN ProductVariant ON (ProductVariant.ProductID = Product.ProductID) 
WHERE (1=1) 
    AND (CONTAINS((Product.*), @Keywords) OR CONTAINS((ProductVariant.*), @Keywords)) 
    AND (Product.SourceID = @SourceID) 
    AND (Product.ProductStatus = @ProductStatus) 
    AND (ProductVariant.ProductStatus = @ProductStatus) 

Execution plan (quick query)

查询2(慢)

SELECT DISTINCT
    Product.ProductID, Product.Title 
FROM 
    Product 
    INNER JOIN ProductVariant ON (ProductVariant.ProductID = Product.ProductID) 
WHERE (1=1) 
    AND (CONTAINS((Product.*), @Keywords) OR CONTAINS((ProductVariant.*), @Keywords)) 
    AND (Product.SourceID = @SourceID) 
    AND (Product.DepartmentID = @DepartmentID)
    AND (Product.ProductStatus = @ProductStatus) 
    AND (ProductVariant.ProductStatus = @ProductStatus) 

Execution plan (slow query)

Product和ProductVariant表都有一些参与全文索引的字符串列。 Product表在SourceID列上有一个非聚集索引,另一个在SourceID + DepartmentID上编入索引的非聚簇(这种冗余不是疏忽,而是打算)。 ProductVariant.ProductID是产品的FK,并且在其上具有非聚集索引。将更新所有索引和列的统计信息,SQL Management Studio不会报告丢失的索引。

关于可能导致这种截然不同的表现的任何建议?

P.S。忘记提及Product.DepartmentID是一个部门表的FK,以防它有任何区别。

2 个答案:

答案 0 :(得分:1)

感谢@MartinSmith建议将全文搜索逻辑分解为临时表,然后使用它们来过滤主查询的结果。以下仅在2秒内返回:

SELECT 
    [Key] AS ProductID
INTO 
    #matchingProducts
FROM 
    CONTAINSTABLE(Product, *, @Keywords)

SELECT 
    [Key] AS VariantID
INTO
    #matchingVariants
FROM
    CONTAINSTABLE(ProductVariant, *, @Keywords)

SELECT DISTINCT
    Product.ProductID, Product.Title 
FROM 
    Product 
    INNER JOIN ProductVariant ON (ProductVariant.ProductID = Product.ProductID) 
    LEFT OUTER JOIN #matchingProducts ON #matchingProducts.ProductID = Product.ProductID
    LEFT OUTER JOIN #matchingVariants ON #matchingVariants.VariantID = ProductVariant.VariantID
WHERE (1=1) 
    AND (Product.SourceID = @SourceID) 
    AND (Product.ProductStatus = @ProductStatus) 
    AND (ProductVariant.ProductStatus = @ProductStatus) 
    AND (Product.DepartmentID = @DepartmentID) 
    AND (NOT #matchingProducts.ProductID IS NULL OR NOT #matchingVariants.VariantID IS NULL)

奇怪的是,当我尝试使用如下所示的嵌套查询来简化上述解决方案时,结果在速度方面介于两者之间(大约25秒)。从理论上讲,下面的查询应该与上面的查询相同,但不知何故,SQL Server在内部以不同的方式编译第二个查询。

SELECT DISTINCT
    Product.ProductID, Product.Title 
FROM 
    Product 
    INNER JOIN ProductVariant ON (ProductVariant.ProductID = Product.ProductID) 
    LEFT OUTER JOIN 
    (
        SELECT 
            [Key] AS ProductID
        FROM 
            CONTAINSTABLE(Product, *, @Keywords)
    ) MatchingProducts 
    ON MatchingProducts.ProductID = Product.ProductID
    LEFT OUTER JOIN 
    (
        SELECT 
            [Key] AS VariantID
        FROM
            CONTAINSTABLE(ProductVariant, *, @Keywords)
    ) MatchingVariants 
    ON MatchingVariants.VariantID = ProductVariant.VariantID
WHERE (1=1) 
    AND (Product.SourceID = @SourceID) 
    AND (Product.ProductStatus = @ProductStatus) 
    AND (ProductVariant.ProductStatus = @ProductStatus) 
    AND (Product.DepartmentID = @DepartmentID) 
    AND (NOT MatchingProducts.ProductID IS NULL OR NOT MatchingVariants.VariantID IS NULL)

答案 1 :(得分:-1)

这可能是你的错误

  

为了避免不必要的连接,我们将实际的搜索查询构建为动态SQL并在存储过程中执行它。

在大多数情况下,服务器无法优化动态SQL。有一些技术可以缓解这种情况。阅读更多The Curse and Blessings of Dynamic SQL

摆脱动态SQL并使用适当的索引构建一个体面的查询。我向你保证;在优化方面,SQL Server比你更了解。如果必须(或一百个),则定义十个不同的查询。

其次......为什么在运行不同的查询时,您会期望相同的执行计划?使用不同的列/索引?根据您的方法,您获得的执行计划和结果对我来说似乎很自然。

您不会获得相同的执行计划/性能,因为您没有在第一个查询中查询列'DepartmentID'。