Я использую Entity Framework (сначала код) и поиск порядка, который я определяю в моих запросах LINQ, оказывает огромное влияние на производительность, например:
using (var db = new MyDbContext())
{
var mySize = "medium";
var myColour = "vermilion";
var list1 = db.Widgets.Where(x => x.Colour == myColour && x.Size == mySize).ToList();
var list2 = db.Widgets.Where(x => x.Size == mySize && x.Colour == myColour).ToList();
}
В тех случаях, когда предложение (редкое) цветовое предложение предшествует (общему) условию размера быстро, но наоборот наоборот медленнее. В таблице содержится несколько миллионов строк, и два рассматриваемых поля - nvarchar (50), поэтому не нормируются, но каждый индексируется. Поля указаны в первом порядке кода следующим образом:
[StringLength(50)]
public string Colour { get; set; }
[StringLength(50)]
public string Size { get; set; }
Я действительно должен был беспокоиться о таких вещах в моих запросах LINQ, я думал, что это была работа с базой данных?
Системные спецификации:
- Visual Studio 2010
- .NET 4
- EntityFramework 6.0.0-beta1
- Сервер SQL Server 2008 R2 (64 бит)
Обновление:
Правильно, для любых обжарок для наказания эффект можно воспроизвести, как показано ниже. Этот вопрос, судя по всему, чрезвычайно чувствителен к ряду факторов, поэтому, пожалуйста, воздержитесь от надуманного характера:
Установите EntityFramework 6.0.0-beta1 через nuget, затем сгенерируйте первый стиль кода с помощью:
public class Widget
{
[Key]
public int WidgetId { get; set; }
[StringLength(50)]
public string Size { get; set; }
[StringLength(50)]
public string Colour { get; set; }
}
public class MyDbContext : DbContext
{
public MyDbContext()
: base("DefaultConnection")
{
}
public DbSet<Widget> Widgets { get; set; }
}
Создайте фиктивные данные со следующим SQL:
insert into gadget (Size, Colour)
select RND1 + ' is the name is this size' as Size,
RND2 + ' is the name of this colour' as Colour
from (Select top 1000000
CAST(abs(Checksum(NewId())) % 100 as varchar) As RND1,
CAST(abs(Checksum(NewId())) % 10000 as varchar) As RND2
from master..spt_values t1 cross join master..spt_values t2) t3
Добавьте по одному индексу для цвета и размера, затем выполните запрос:
string mySize = "99 is the name is this size";
string myColour = "9999 is the name of this colour";
using (var db = new WebDbContext())
{
var list1= db.Widgets.Where(x => x.Colour == myColour && x.Size == mySize).ToList();
}
using (var db = new WebDbContext())
{
var list2 = db.Widgets.Where(x => x.Size == mySize && x.Colour == myColour).ToList();
}
Проблема связана с тупой коллекцией сравнений NULL в сгенерированном SQL, как показано ниже.
exec sp_executesql N'SELECT
[Extent1].[WidgetId] AS [WidgetId],
[Extent1].[Size] AS [Size],
[Extent1].[Colour] AS [Colour]
FROM [dbo].[Widget] AS [Extent1]
WHERE ((([Extent1].[Size] = @p__linq__0)
AND ( NOT ([Extent1].[Size] IS NULL OR @p__linq__0 IS NULL)))
OR (([Extent1].[Size] IS NULL) AND (@p__linq__0 IS NULL)))
AND ((([Extent1].[Colour] = @p__linq__1) AND ( NOT ([Extent1].[Colour] IS NULL
OR @p__linq__1 IS NULL))) OR (([Extent1].[Colour] IS NULL)
AND (@p__linq__1 IS NULL)))',N'@p__linq__0 nvarchar(4000),@p__linq__1 nvarchar(4000)',
@p__linq__0=N'99 is the name is this size',
@p__linq__1=N'9999 is the name of this colour'
go
Изменение оператора равенства в LINQ to StartWith() заставляет проблему уйти, как и изменение одного из двух полей, которые не могут быть обнулены в базе данных.
Я отчаиваюсь!
Обновление 2:
Некоторая помощь для любых охотников за головами, проблема может быть воспроизведена на SQL Server 2008 R2 Web (64 бит) в чистой базе данных следующим образом:
CREATE TABLE [dbo].[Widget](
[WidgetId] [int] IDENTITY(1,1) NOT NULL,
[Size] [nvarchar](50) NULL,
[Colour] [nvarchar](50) NULL,
CONSTRAINT [PK_dbo.Widget] PRIMARY KEY CLUSTERED
(
[WidgetId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX IX_Widget_Size ON dbo.Widget
(
Size
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX IX_Widget_Colour ON dbo.Widget
(
Colour
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
insert into Widget (Size, Colour)
select RND1 + ' is the name is this size' as Size,
RND2 + ' is the name of this colour' as Colour
from (Select top 1000000
CAST(abs(Checksum(NewId())) % 100 as varchar) As RND1,
CAST(abs(Checksum(NewId())) % 10000 as varchar) As RND2
from master..spt_values t1 cross join master..spt_values t2) t3
GO
а затем сравните относительную производительность следующих двух запросов (вам может потребоваться настроить значения параметров параметров, чтобы получить запрос, который возвращает пару строк, чтобы наблюдать эффект, то есть второй идентификатор запроса намного медленнее).
exec sp_executesql N'SELECT
[Extent1].[WidgetId] AS [WidgetId],
[Extent1].[Size] AS [Size],
[Extent1].[Colour] AS [Colour]
FROM [dbo].[Widget] AS [Extent1]
WHERE ((([Extent1].[Colour] = @p__linq__0)
AND ( NOT ([Extent1].[Colour] IS NULL
OR @p__linq__0 IS NULL)))
OR (([Extent1].[Colour] IS NULL)
AND (@p__linq__0 IS NULL)))
AND ((([Extent1].[Size] = @p__linq__1)
AND ( NOT ([Extent1].[Size] IS NULL
OR @p__linq__1 IS NULL)))
OR (([Extent1].[Size] IS NULL) AND (@p__linq__1 IS NULL)))',
N'@p__linq__0 nvarchar(4000),@p__linq__1 nvarchar(4000)',
@p__linq__0=N'9999 is the name of this colour',
@p__linq__1=N'99 is the name is this size'
go
exec sp_executesql N'SELECT
[Extent1].[WidgetId] AS [WidgetId],
[Extent1].[Size] AS [Size],
[Extent1].[Colour] AS [Colour]
FROM [dbo].[Widget] AS [Extent1]
WHERE ((([Extent1].[Size] = @p__linq__0)
AND ( NOT ([Extent1].[Size] IS NULL
OR @p__linq__0 IS NULL)))
OR (([Extent1].[Size] IS NULL)
AND (@p__linq__0 IS NULL)))
AND ((([Extent1].[Colour] = @p__linq__1)
AND ( NOT ([Extent1].[Colour] IS NULL
OR @p__linq__1 IS NULL)))
OR (([Extent1].[Colour] IS NULL)
AND (@p__linq__1 IS NULL)))',
N'@p__linq__0 nvarchar(4000),@p__linq__1 nvarchar(4000)',
@p__linq__0=N'99 is the name is this size',
@p__linq__1=N'9999 is the name of this colour'
Вы также можете найти, как и я, что, если вы перезапустите фиктивную вставку данных, чтобы было теперь два миллиона строк, проблема исчезнет.