linq to sql startwith 性能索引列

Posted

技术标签:

【中文标题】linq to sql startwith 性能索引列【英文标题】:linq to sql startwith performance indexed columns 【发布时间】:2014-11-13 21:18:53 【问题描述】:

我首先使用实体​​代码。 索引列:

SourceCatalogId 已禁用 类别路径

表格中有 40 000 行,

我的问题是查询需要 40 秒!!

var result = DBContext.Set<SourceProduct>()
            .Include(x => x.SalesHistories, x => x.SourceCatalog)
            .Where(p => p.SourceCatalogId == 2)
            .where(p => p.Disabled == false)
            .where(x => x.CategoryPath.StartsWith("MyPath"))
            .orderby(x => x.ShortDesignation)
            .Skip(1)
            .Take(10)
            .toList();

通过 sql profiler 的 SQL:

exec sp_executesql N'SELECT TOP (10) 
[Project1].[SourceProductId] AS [SourceProductId], 
[Project1].[SourceSKU] AS [SourceSKU], 
[Project1].[SourceCatalogId] AS [SourceCatalogId], 
[Project1].[ManufacturerReference] AS [ManufacturerReference], 
[Project1].[Disabled] AS [Disabled], 
[Project1].[EAN] AS [EAN], 
[Project1].[ShortDesignation] AS [ShortDesignation], 
[Project1].[FullDesignation] AS [FullDesignation], 
[Project1].[Description] AS [Description], 
[Project1].[Url] AS [Url], 
[Project1].[CategoryPath] AS [CategoryPath], 
[Project1].[Condition] AS [Condition], 
[Project1].[BuyingPriceHT] AS [BuyingPriceHT], 
[Project1].[ShippingPriceHT] AS [ShippingPriceHT], 
[Project1].[PublicSellingPriceHT] AS [PublicSellingPriceHT], 
[Project1].[PictureUrl1] AS [PictureUrl1], 
[Project1].[PictureUrl2] AS [PictureUrl2], 
[Project1].[PictureUrl3] AS [PictureUrl3], 
[Project1].[PictureUrl4] AS [PictureUrl4], 
[Project1].[Quantity] AS [Quantity], 
[Project1].[AddDate] AS [AddDate], 
[Project1].[UpdateDate] AS [UpdateDate], 
[Project1].[Followers] AS [Followers]
FROM ( SELECT [Project1].[SourceProductId] AS [SourceProductId], [Project1].[SourceSKU] AS [SourceSKU], [Project1].[SourceCatalogId] AS [SourceCatalogId], [Project1].[ManufacturerReference] AS [ManufacturerReference], [Project1].[Disabled] AS [Disabled], [Project1].[EAN] AS [EAN], [Project1].[ShortDesignation] AS [ShortDesignation], [Project1].[FullDesignation] AS [FullDesignation], [Project1].[Description] AS [Description], [Project1].[Url] AS [Url], [Project1].[CategoryPath] AS [CategoryPath], [Project1].[Condition] AS [Condition], [Project1].[BuyingPriceHT] AS [BuyingPriceHT], [Project1].[ShippingPriceHT] AS [ShippingPriceHT], [Project1].[PublicSellingPriceHT] AS [PublicSellingPriceHT], [Project1].[PictureUrl1] AS [PictureUrl1], [Project1].[PictureUrl2] AS [PictureUrl2], [Project1].[PictureUrl3] AS [PictureUrl3], [Project1].[PictureUrl4] AS [PictureUrl4], [Project1].[Quantity] AS [Quantity], [Project1].[AddDate] AS [AddDate], [Project1].[UpdateDate] AS [UpdateDate], [Project1].[Followers] AS [Followers], row_number() OVER (ORDER BY [Project1].[ShortDesignation] ASC) AS [row_number]
    FROM ( SELECT 
        [Extent1].[SourceProductId] AS [SourceProductId], 
        [Extent1].[SourceSKU] AS [SourceSKU], 
        [Extent1].[SourceCatalogId] AS [SourceCatalogId], 
        [Extent1].[ManufacturerReference] AS [ManufacturerReference], 
        [Extent1].[Disabled] AS [Disabled], 
        [Extent1].[EAN] AS [EAN], 
        [Extent1].[ShortDesignation] AS [ShortDesignation], 
        [Extent1].[FullDesignation] AS [FullDesignation], 
        [Extent1].[Description] AS [Description], 
        [Extent1].[Url] AS [Url], 
        [Extent1].[CategoryPath] AS [CategoryPath], 
        [Extent1].[Condition] AS [Condition], 
        [Extent1].[BuyingPriceHT] AS [BuyingPriceHT], 
        [Extent1].[ShippingPriceHT] AS [ShippingPriceHT], 
        [Extent1].[PublicSellingPriceHT] AS [PublicSellingPriceHT], 
        [Extent1].[PictureUrl1] AS [PictureUrl1], 
        [Extent1].[PictureUrl2] AS [PictureUrl2], 
        [Extent1].[PictureUrl3] AS [PictureUrl3], 
        [Extent1].[PictureUrl4] AS [PictureUrl4], 
        [Extent1].[Quantity] AS [Quantity], 
        [Extent1].[AddDate] AS [AddDate], 
        [Extent1].[UpdateDate] AS [UpdateDate], 
        [Extent1].[Followers] AS [Followers]
        FROM [dbo].[SourceProducts] AS [Extent1]
        WHERE ([Extent1].[SourceCatalogId] = @p__linq__0) AND (0 = [Extent1].[Disabled]) AND ([Extent1].[CategoryPath] LIKE @p__linq__1 ESCAPE N''~'')
    )  AS [Project1]
)  AS [Project1]
WHERE [Project1].[row_number] > 0
ORDER BY [Project1].[ShortDesignation] ASC',N'@p__linq__0 bigint,@p__linq__1 nvarchar(4000)',@p__linq__0=2,@p__linq__1=N'MyPath%'

在 where 子句之前的最后一个,如果我删除 "escape N''~''" in:

WHERE ([Extent1].[SourceCatalogId] = @p__linq__0) AND (0 = [Extent1].[Disabled]) AND ([Extent1].[CategoryPath] LIKE @p__linq__1 ESCAPE N''~'')

查询耗时 4s。

正常吗?索引用途 ?我如何用 startWith 解决它?

编辑

categoryPath 的索引属性:

[Index("IX_SourceProduct_SourceCatalogId_Disabled_CategoryPath", 3), StringLength(400)]
    public string CategoryPath  get; set; 

EDIT2

好吧,我已经很接近了,我认为问题是存储过程。

string search = "julien";
            var list = db.Users.Where(x => x.Name.StartsWith(search));
            string query = list.ToString();

=> 选择 [Extent1].[UserId] AS [UserId], [Extent1].[名称] AS [名称] FROM [dbo].[Users] AS [Extent1] WHERE [Extent1].[Name] LIKE @p__linq__0 ESCAPE N'~'

var list2 = db.Users.Where(x => x.Name.StartsWith("julien"));
            string query2 = list2.ToString();

=> 选择 [Extent1].[UserId] AS [UserId], [Extent1].[名称] AS [名称] FROM [dbo].[Users] AS [Extent1] WHERE [Extent1].[Name] LIKE N'julien%'

所以如果我在查询中使用变量来获取存储过程,如果我使用 const 我会得到选择。

在存储过程中(由实体生成)使得出现@p__linq__0所以添加ESCAPE N'~' 避免变量中出现 wildCaractere。

所以现在问题更简单了。如何避免使用变量查询?这是可能的 ? 谢谢

【问题讨论】:

刚找到这个帖子,不明白有没有解决办法? ***.com/questions/20496098/… 您使用的是全文索引,还是仅使用普通索引(这对您进行部分字符串匹配没有帮助)? @Mashton 我不明白你的问题,但这就是我用代码索引的方式首先看到第一个编辑 全文索引用于加快字符串模式匹配,与普通的列索引不同。索引列意味着它可以更快地找到精确的列匹配,但 FTI 允许您匹配那些字符串的位。您将无法使用您的linq.StartsWith,因为 FTI 要求您使用 sql 命令CONTAINS,这意味着您必须开始调用存储过程。但是速度的提升是值得的。对于我们正在做的查询,我们从 1 分 30 秒到 simple-talk.com/sql/learn-sql-server/… @Mashon 谢谢你的回答。现在对全文索引更清楚了。但是我只需要stratWith功能,简单的索引就足够了(对于包含功能你是对的,需要全文)。当我在 sql 查询中删除“ESCAPE N''~''”时,我可以检查我在说什么。查询非常快,我在 sql profiler 中看到我使用了索引。如何禁用实体中的转义功能?看起来已经关闭了:***.com/questions/20496098/… 【参考方案1】:

因此,您需要在此处获取变量的值并将其用作您正在生成的Expression 中的常量。这其实是很有可能的。我们需要的是一个表达式,它接受你想要的参数作为你的真实选择器的参数,作为第二个参数,它是常量值的占位符,然后是你想要成为常量的值。然后我们可以用常量的值替换参数的所有实例,只留下一个将实际参数映射到结果的函数:

public static Expression<Func<TSource, TResult>> EmbedConstant
    <TSource, TResult, TConstant>(
    this Expression<Func<TSource, TConstant, TResult>> expression,
    TConstant constant)

    var body = expression.Body.Replace(
        expression.Parameters[1],
        Expression.Constant(constant));
    return Expression.Lambda<Func<TSource, TResult>>(
        body, expression.Parameters[0]);

这依赖于以下方法将一个表达式的所有实例替换为另一个:

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)

    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);

internal class ReplaceVisitor : ExpressionVisitor

    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    
        this.from = from;
        this.to = to;
    
    public override Expression Visit(Expression node)
    
        return node == from ? to : base.Visit(node);
    

这允许你映射这个:

string search = "julien";
var list = db.Users.Where(x => x.Name.StartsWith(search));
string query = list.ToString();

进入这个:

string search = "julien";
Expression<Func<User, string, bool>> predicate = 
    (item, searchTerm) => item.Name.StartsWith(searchTerm);
var list = db.Users.Where(predicate.EmbedConstant(search));
string query = list.ToString();

【讨论】:

效果太棒了!!!花了一天时间...非常感谢。我可以给你一只熊吗?)(就像我们在法国说的谢谢) WTF?所有这些代码只是为了在查询中包含 Contains 或 StartWIth ???没有这个..执行起来会很慢。 如何将它与 LINQ 一起使用来反对?它给了我错误 试图为 linq 执行此操作以对象 var 结果 = (from I in db.mytable where i.mychildtable.col1.contains(search) select I).tolist()); 知道将上述代码放在哪里会很有帮助。

以上是关于linq to sql startwith 性能索引列的主要内容,如果未能解决你的问题,请参考以下文章

在 LINQ-To-SQL 中,我应该使用 NOLOCK 来提高性能吗?

为啥使用 EF / Linq to sql 创建性能不佳的查询如此容易[关闭]

在 Windows Phone 上使用 Linq to SQL 时,是不是可以提高批量删除的性能?

C# Linq To SQL 插入单个大行性能问题

linq to sql、Entity Framework 和 NHibernate 的性能如何?

Linq-to-SQL 数据检索速度比较