实体框架使用 PatIndex 生成的查询不起作用

Posted

技术标签:

【中文标题】实体框架使用 PatIndex 生成的查询不起作用【英文标题】:Query genereated by Entity Framework using PatIndex not working 【发布时间】:2013-05-30 12:22:27 【问题描述】:

我有一个由 EF5 代码生成的查询,我在其中使用 SqlFunctions.PatIndex,该查询需要 5 分钟才能运行。如果我对 SQL 进行更改以使用 like 运算符,那么它会在亚秒内工作。从我读过的内容来看,PatIndex 应该至少与 like 运算符一样好用。这似乎是与 row_number() OVER 部分的一些交互,因为如果您只使用 patindex 子句执行最里面的选择,它也会在亚秒时间内返回。谁能告诉我为什么 row_number() OVER 正在杀死 PATINDEX 查询而不是使用 like 的查询?

这个有效:

exec sp_executesql N'SELECT TOP (10) 
[Project1].[C1] AS [C1], 
[Project1].[PersonId] AS [PersonId], 
[Project1].[FirstName] AS [FirstName], 
[Project1].[MiddleName] AS [MiddleName], 
[Project1].[LastName] AS [LastName], 
[Project1].[Suffix] AS [Suffix], 
[Project1].[DateOfBirth] AS [DateOfBirth], 
[Project1].[Ssn] AS [Ssn], 
[Project1].[CenterNumber] AS [CenterNumber], 
[Project1].[GuarantorNumber] AS [GuarantorNumber], 
[Project1].[GUNumber] AS [GUNumber], 
[Project1].[IndustrialClientNumber] AS [IndustrialClientNumber], 
[Project1].[Employer] AS [Employer]
FROM ( SELECT [Project1].[PersonId] AS [PersonId], [Project1].[CenterNumber] AS [CenterNumber], [Project1].[GuarantorNumber] AS [GuarantorNumber], [Project1].[GUNumber] AS [GUNumber], [Project1].[IndustrialClientNumber] AS [IndustrialClientNumber], [Project1].[Employer] AS [Employer], [Project1].[FirstName] AS [FirstName], [Project1].[MiddleName] AS [MiddleName], [Project1].[LastName] AS [LastName], [Project1].[Suffix] AS [Suffix], [Project1].[DateOfBirth] AS [DateOfBirth], [Project1].[Ssn] AS [Ssn], [Project1].[C1] AS [C1], row_number() OVER (ORDER BY [Project1].[FirstName] ASC) AS [row_number]
       FROM ( SELECT 
              [Extent1].[PersonId] AS [PersonId], 
              [Extent1].[CenterNumber] AS [CenterNumber], 
              [Extent1].[GuarantorNumber] AS [GuarantorNumber], 
              [Extent1].[GUNumber] AS [GUNumber], 
              [Extent1].[IndustrialClientNumber] AS [IndustrialClientNumber], 
              [Extent1].[Employer] AS [Employer], 
              [Extent2].[FirstName] AS [FirstName], 
              [Extent2].[MiddleName] AS [MiddleName], 
              [Extent2].[LastName] AS [LastName], 
              [Extent2].[Suffix] AS [Suffix], 
              [Extent2].[DateOfBirth] AS [DateOfBirth], 
              [Extent2].[Ssn] AS [Ssn], 
              ''0X0X'' AS [C1]
              FROM  [dbo].[vGuarantor] AS [Extent1]
              INNER JOIN [dbo].[vPerson] AS [Extent2] ON [Extent1].[PersonId] = [Extent2].[PersonId]
              WHERE ([Extent1].[CenterNumber] = @p__linq__0) AND ***([Extent1].[GuarantorNumber] like @p__linq__1)***
       )  AS [Project1]
)  AS [Project1]
WHERE [Project1].[row_number] > 0
ORDER BY [Project1].[FirstName] ASC
',N'@p__linq__0 nvarchar(4000),@p__linq__1 nvarchar(4000)',@p__linq__0=N'2',@p__linq__1=N'13100%'

这个没有:

exec sp_executesql N'SELECT TOP (10) 
[Project1].[C1] AS [C1], 
[Project1].[PersonId] AS [PersonId], 
[Project1].[FirstName] AS [FirstName], 
[Project1].[MiddleName] AS [MiddleName], 
[Project1].[LastName] AS [LastName], 
[Project1].[Suffix] AS [Suffix], 
[Project1].[DateOfBirth] AS [DateOfBirth], 
[Project1].[Ssn] AS [Ssn], 
[Project1].[CenterNumber] AS [CenterNumber], 
[Project1].[GuarantorNumber] AS [GuarantorNumber], 
[Project1].[GUNumber] AS [GUNumber], 
[Project1].[IndustrialClientNumber] AS [IndustrialClientNumber], 
[Project1].[Employer] AS [Employer]
FROM ( SELECT [Project1].[PersonId] AS [PersonId], [Project1].[CenterNumber] AS [CenterNumber], [Project1].[GuarantorNumber] AS [GuarantorNumber], [Project1].[GUNumber] AS [GUNumber], [Project1].[IndustrialClientNumber] AS [IndustrialClientNumber], [Project1].[Employer] AS [Employer], [Project1].[FirstName] AS [FirstName], [Project1].[MiddleName] AS [MiddleName], [Project1].[LastName] AS [LastName], [Project1].[Suffix] AS [Suffix], [Project1].[DateOfBirth] AS [DateOfBirth], [Project1].[Ssn] AS [Ssn], [Project1].[C1] AS [C1], row_number() OVER (ORDER BY [Project1].[FirstName] ASC) AS [row_number]
       FROM ( SELECT 
              [Extent1].[PersonId] AS [PersonId], 
              [Extent1].[CenterNumber] AS [CenterNumber], 
              [Extent1].[GuarantorNumber] AS [GuarantorNumber], 
              [Extent1].[GUNumber] AS [GUNumber], 
              [Extent1].[IndustrialClientNumber] AS [IndustrialClientNumber], 
              [Extent1].[Employer] AS [Employer], 
              [Extent2].[FirstName] AS [FirstName], 
              [Extent2].[MiddleName] AS [MiddleName], 
              [Extent2].[LastName] AS [LastName], 
              [Extent2].[Suffix] AS [Suffix], 
              [Extent2].[DateOfBirth] AS [DateOfBirth], 
              [Extent2].[Ssn] AS [Ssn], 
              ''0X0X'' AS [C1]
              FROM  [dbo].[vGuarantor] AS [Extent1]
              INNER JOIN [dbo].[vPerson] AS [Extent2] ON [Extent1].[PersonId] = [Extent2].[PersonId]
              WHERE ([Extent1].[CenterNumber] = @p__linq__0) AND ***(PATINDEX(@p__linq__1, [Extent1].[GuarantorNumber]) > 0)***
       )  AS [Project1]
)  AS [Project1]
WHERE [Project1].[row_number] > 0
ORDER BY [Project1].[FirstName] ASC
',N'@p__linq__0 nvarchar(4000),@p__linq__1 nvarchar(4000)',@p__linq__0=N'2',@p__linq__1=N'13100%'

这是两张表:

/****** Object:  Table [Person]    Script Date: 5/30/2013 8:17:15 AM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

SET ANSI_PADDING ON
GO

CREATE TABLE [Person](
    [PersonId] [uniqueidentifier] NOT NULL,
    [FirstName] [nvarchar](255) NULL,
    [MiddleName] [nvarchar](255) NULL,
    [LastName] [nvarchar](255) NULL,
    [DateOfBirth] [datetime] NULL,
    [DateOfBirthIsGuess] [bit] NULL,
    [NickName] [nvarchar](255) NULL,
    [Suffix] [nchar](10) NULL,
    [Gender] [char](1) NULL,
    [SSN] [numeric](9, 0) NULL,
 CONSTRAINT [PK__Person__04E4BC85] PRIMARY KEY CLUSTERED 
(
    [PersonId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 95) ON [PRIMARY]
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

/****** Object:  Index [PK__Person__04E4BC85]    Script Date: 7/18/2013 8:19:16 AM ******/
ALTER TABLE [PatientFirst].[Person] ADD  CONSTRAINT [PK__Person__04E4BC85] PRIMARY KEY CLUSTERED 
(
    [PersonId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 95) ON [PRIMARY]
GO

/****** Object:  Index [IDX_Person.LastName]    Script Date: 7/18/2013 8:19:24 AM ******/
CREATE NONCLUSTERED INDEX [IDX_Person.LastName] ON [PatientFirst].[Person]
(
    [LastName] ASC
)
INCLUDE (   [DateOfBirth],
    [FirstName],
    [MiddleName],
    [PersonId],
    [SSN],
    [Suffix]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

/****** Object:  Index [IDX_Person.FirstName]    Script Date: 7/18/2013 8:19:37 AM ******/
CREATE NONCLUSTERED INDEX [IDX_Person.FirstName] ON [PatientFirst].[Person]
(
    [FirstName] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

/****** Object:  Index [IDX_Person.DOB]    Script Date: 7/18/2013 8:19:47 AM ******/
CREATE NONCLUSTERED INDEX [IDX_Person.DOB] ON [PatientFirst].[Person]
(
    [DateOfBirth] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

/****** Object:  Table [Guarantor]    Script Date: 5/30/2013 8:17:11 AM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

SET ANSI_PADDING ON
GO

CREATE TABLE [Guarantor](
    [GuarantorId] [uniqueidentifier] NOT NULL,
    [CenterNumber] [nvarchar](10) NOT NULL,
    [GuarantorNumber] [nvarchar](10) NOT NULL,
    [IndustrialClientNumber] [int] NULL,
    [Employer] [varchar](4000) NULL,
    [ImportDate] [datetime] NULL,
    [GUNumber]  AS (([CenterNumber]+'*')+[GuarantorNumber]),
 CONSTRAINT [PK__Guarantor__44FF419A] PRIMARY KEY CLUSTERED 
(
    [GuarantorId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 95) ON [PRIMARY]
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

ALTER TABLE [Guarantor] ADD  CONSTRAINT [DF__Guarantor__Guara__45F365D3]  DEFAULT (newid()) FOR [GuarantorId]
GO

ALTER TABLE [Guarantor] ADD  CONSTRAINT [DF_Guarantor_ImportDate]  DEFAULT (getdate()) FOR [ImportDate]
GO

ALTER TABLE [Guarantor]  WITH CHECK ADD  CONSTRAINT [FK_Guarantor_Person] FOREIGN KEY([GuarantorId])
REFERENCES [Person] ([PersonId])
GO

ALTER TABLE [Guarantor] CHECK CONSTRAINT [FK_Guarantor_Person]
GO
/****** Object:  Index [PK__Guarantor__44FF419A]    Script Date: 7/18/2013 8:17:43 AM ******/
ALTER TABLE [PatientFirst].[Guarantor] ADD  CONSTRAINT [PK__Guarantor__44FF419A] PRIMARY KEY CLUSTERED 
(
    [GuarantorId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 95) ON [PRIMARY]
GO


/****** Object:  Index [IDX_Guarantor_CenterNumber]    Script Date: 7/18/2013 8:16:40 AM ******/
CREATE NONCLUSTERED INDEX [IDX_Guarantor_CenterNumber] ON [PatientFirst].[Guarantor]
(
    [CenterNumber] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO


/****** Object:  Index [IDX_Guarantor_CenterNumberGuarantorNumber]    Script Date: 7/18/2013 8:16:45 AM ******/
CREATE UNIQUE NONCLUSTERED INDEX [IDX_Guarantor_CenterNumberGuarantorNumber] ON [PatientFirst].[Guarantor]
(
    [CenterNumber] ASC,
    [GuarantorNumber] 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, FILLFACTOR = 95) ON [PRIMARY]
GO


/****** Object:  Index [IDX_Guarantor_GuarantorNumber]    Script Date: 7/18/2013 8:17:01 AM ******/
CREATE NONCLUSTERED INDEX [IDX_Guarantor_GuarantorNumber] ON [PatientFirst].[Guarantor]
(
    [GuarantorNumber] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO


/****** Object:  Index [IDX_Guarantor_GUNumber]    Script Date: 7/18/2013 8:17:09 AM ******/
CREATE NONCLUSTERED INDEX [IDX_Guarantor_GUNumber] ON [PatientFirst].[Guarantor]
(
    [GUNumber] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 95) ON [PRIMARY]
GO


/****** Object:  Index [IDX_Guarantor_ImportDate]    Script Date: 7/18/2013 8:17:21 AM ******/
CREATE NONCLUSTERED INDEX [IDX_Guarantor_ImportDate] ON [PatientFirst].[Guarantor]
(
    [ImportDate] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 95) ON [PRIMARY]
GO

/****** Object:  Index [IDX_Guarantor_IndustrialClientNumber]    Script Date: 7/18/2013 8:17:30 AM ******/
CREATE NONCLUSTERED INDEX [IDX_Guarantor_IndustrialClientNumber] ON [PatientFirst].[Guarantor]
(
    [IndustrialClientNumber] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 95) ON [PRIMARY]
GO

查询中使用的视图只是从基础表中选择 *。

like 算子的执行计划:

https://docs.google.com/file/d/0B4uS27e7ZMfCOTl5LS1UaHBSOTQ/edit?usp=sharing

Patindex 计划: https://docs.google.com/file/d/0B4uS27e7ZMfCVTljNml4R0xjT28/edit?usp=sharing

【问题讨论】:

使用慢查询创建一个工作负载,通过数据库引擎优化顾问运行它,然后查看它的建议。 这不是索引,我已经这样做了,like 运算符返回正常,但 patindex 没有。 您要搜索的字符串是什么? 我正在搜索“13100%” 在这种情况下,使用LIKE 的查询将能够使用索引查找来查找值,而使用PATINDEX 的版本将使用索引扫描卡住。 【参考方案1】:

我怀疑“包含”搜索(例如:%3%00%)的两个查询的性能同样糟糕。但是,如果您的大部分搜索都包含前缀(例如:131%00%),您可以通过添加StartsWith 条件来提高性能。

例如:

var query = context.Guarantors.Where(g => g.CenterNumber == centerNumber);

int index = guarantorNumber.IndexOfAny(new[] '%', '_', '[' );
switch (index)

    case -1:
    
        query = query.Where(g => g.GuarantorNumber == guarantorNumber);
        break;
    
    case 0:
    
        // Nothing we can do here. The query performance will suck.
        query = query.Where(g => SqlFunctions.PatIndex(guarantorNumber, g.GuarantorNumber) > 0);
        break;
    
    default:
    
        string prefix = guarantorNumber.Substring(0, index);
        query = query.Where(g => g.GuarantorNumber.StartsWith(prefix));
        if (index != guarantorNumber.Length - 1 || guarantorNumber[index] != '%')
        
            query = query.Where(g => SqlFunctions.PatIndex(guarantorNumber, g.GuarantorNumber) > 0);
        
        break;
    


注意:您也可以尝试更新您的统计信息。执行计划显示两个表的估计行和实际行之间存在显着差异。

担保人:估计7188,实际107345 人员:估计7102,实际6858694

从巨大的差异来看,我猜你已经关闭了 Auto Update Statistics 选项,并且你还没有安排一个作业来手动更新统计信息。

尝试以下命令:

UPDATE STATISTICS Person;
UPDATE STATISTICS Guarantor;

这应该为 SQL 提供足够的信息来选择更明智的执行计划。

UPDATE STATISTICS topic on MSDN

【讨论】:

patindex 相当糟糕,2:50 对 0:37。我读过的所有内容都表明 patindex 的性能应该更高,而不是更低。 @BlackICE:我不确定你在哪里读到PatIndex 会比Like 更好——如果有的话,我希望它会反过来。如果查看查询计划,您会发现Like 查询(带有前缀)能够读取Guarantor 表上的一小部分索引,而PatIndex 查询必须读取整件事。 @BlackICE:另外,尝试更新您的统计数据。我已经编辑了我的答案来解释。 ***.com/questions/8052425/… blogs.msdn.com/b/deepakbi/archive/2012/07/25/… 我运行了更新统计信息,尽管我很确定我已经运行了它们,差别不大,但这可能是缓存,我得到的结果类似于运算符查询返回亚秒,patindex 返回〜5分钟。

以上是关于实体框架使用 PatIndex 生成的查询不起作用的主要内容,如果未能解决你的问题,请参考以下文章

LINQ 实体框架查询在 EF Core 中不起作用,引发异常

在实体框架核心中的 SelectMany + Select 之后“包含”不起作用

数据库第一实体框架更新模型不起作用:可能是什么原因?

加入前包含不起作用实体框架6

实体框架 4 函数导入不起作用

VS 2010 - 带有 MySql 存储过程的实体框架似乎不起作用