我在SQL Server中有一个非常简单的表,该表具有复合主键。该表非常小,只有不到10 MB的数据。我正在使用SqlMetal自动生成的数据上下文在C#中使用表。我用一些初始数据填充了表格,当一个简单的选择花了30多秒钟完成查询时,我感到很惊讶!我以为我应该能够在不到一秒钟的时间内从磁盘读取所有10 MB的数据并将其通过我们的千兆网络推送。我通过通过Microsoft的SSMS运行相同的查询来验证这是正确的。
因此,我开始研究数据以弄清楚发生了什么。我发现,如果我从表中删除了主键(并重新生成了SqlMetal文件),查询将在不到一秒钟的时间内返回我的数据。完善!除外,因为没有主键。我添加了密钥,重新生成了SqlMetal文件,然后再次运行查询。再慢一点!接下来,我从表中删除了密钥,而没有重新生成SqlMetal文件。还是慢!我认为所有这些都告诉我问题出在客户端,也许是在linq-to-sql的实现或SqlMetal生成的数据上下文中。
因此,我开始创建一些测试代码和数据,我可以在此站点上共享它们。我用相同的架构创建了一个测试数据库表,使用代码填充了大致相同数量的数据,当然,简单的查询也花费了很长时间。我运行它以确保一切正常。。。瞧,查询运行得相当快。我的实际数据值得怪吗?
因此,我尝试使测试数据尽可能接近真实数据。我的实际数据具有由bigint
和nvarchar(64)
组成的自然键。然后,我将另一个bigint
作为第三个元素添加到主键中,以管理版本。当有新项目添加到数据集中时,我给它一个唯一的ID
(bigint
),并给它一个等于其ID的版本ID。对该元素的后续更改将生成新的版本ID(bigint
)。因此,当我最初用所有新元素填充表时,所有版本ID都等于元素的ID。
我在测试数据中复制了此数据,果然,查询速度变慢了。我试图了解此查询正在发生什么。服务器上的查询执行计划显然不是问题,因为它在使用SSMS的任何情况下都能快速运行。 linq-to-sql在返回查询结果之前如何处理我的数据?为什么数据的配置文件(即主键值)会影响客户端的处理速度?
如果您想对此进行测试,以下代码可为您提供帮助:
SQL Server表生成:
CREATE TABLE [dbo].[TestClusteredIndex]
(
[VID] [BIGINT] NOT NULL,
[EID] [BIGINT] NOT NULL,
[Filter] [NVARCHAR](64) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
[MID] [NVARCHAR](64) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
[SD] [DATE] NULL,
[ED] [DATE] NULL,
[B] [BIT] NOT NULL,
CONSTRAINT [PK_TestClusteredIndex]
PRIMARY KEY CLUSTERED ([EID] ASC, [Filter] ASC, [VID] ASC)
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[TestHeap](
[VID] [bigint] NOT NULL,
[EID] [bigint] NOT NULL,
[Filter] [nvarchar](64) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
[MID] [nvarchar](64) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
[SD] [date] NULL,
[ED] [date] NULL,
[B] [bit] NOT NULL
) ON [PRIMARY]
GO
使用SqlMetal生成您自己的DataContext。
C#测试代码:
public static void RunTests()
{
FillDB(false);
TestSpeedWithPK(true);
TestSpeedWithoutPK(true);
FillDB(true);
TestSpeedWithPK(true);
TestSpeedWithoutPK(true);
}
public static void FillDB(bool problematic)
{
long l1 = 5000;
long l2 = 750000;
bool dummy = true;
var db = new YourDataContext(@"YourConnectionString");
Console.WriteLine("\nClearing database tables...");
db.ExecuteCommand("DELETE FROM [TestClusteredIndex];");
db.ExecuteCommand("DELETE FROM [TestHeap];");
var rows = new List<TestClusteredIndex>();
Console.WriteLine("Generating " + (problematic ? string.Empty : "non-") + "problematic data...");
for (int i = 0; i < 120000; i++)
{
var row = new TestClusteredIndex
{
EID = problematic ? i : l1++,
VID = problematic ? i : l2++,
Filter = "Filter" + (char)(i % 3 + 65),
MID = (i / 0.5).GetHashCode().ToString(),
SD = null,
ED = null,
B = dummy = !dummy
};
rows.Add(row);
}
Console.WriteLine("Saving " + rows.Count + " rows to table with primary key...");
db.TestClusteredIndex.InsertAllOnSubmit(rows);
db.SubmitChanges();
Console.WriteLine("Copying data to table with no primary key...");
db.ExecuteCommand("INSERT INTO [TestHeap]([VID],[EID],[Filter],[MID],[SD],[ED],[B]) SELECT [VID],[EID],[Filter],[MID],[SD],[ED],[B] FROM [TestClusteredIndex];");
Console.WriteLine("Done creating test data.\n");
}
public static void TestSpeedWithPK(bool displayQuery)
{
Console.Write("Running speed test (primary key: yes)... ");
var db = new YourDataContext(@"YourConnectionString");
var sw = new Stopwatch();
sw.Start();
var items = db.TestClusteredIndex
.Where(x => x.Filter == "FilterA")
.ToList();
sw.Stop();
Console.WriteLine("Queried " + items.Count + " records in " + Math.Round(sw.ElapsedMilliseconds / 1000.0, 2) + " seconds.");
}
public static void TestSpeedWithoutPK(bool displayQuery)
{
Console.Write("Running speed test (primary key: no)... ");
var db = new YourDataContext(@"YourConnectionString");
var sw = new Stopwatch();
sw.Start();
var items = db.TestHeap
.Where(x => x.Filter == "FilterA")
.ToList();
sw.Stop();
Console.WriteLine("Queried " + items.Count + " records in " + Math.Round(sw.ElapsedMilliseconds / 1000.0, 2) + " seconds.");
}
这是我运行测试脚本时的输出:
/*
Clearing database tables...
Generating non-problematic data...
Saving 120000 rows to table with primary key...
Copying data to table with no primary key...
Done creating test data.
Running speed test (primary key: yes)... Queried 40000 records in 0.29 seconds.
@p0:FilterA
SELECT [t0].[VID], [t0].[EID], [t0].[Filter], [t0].[MID], [t0].[SD], [t0].[ED], [t0].[B]
FROM [dbo].[TestClusteredIndex] AS [t0]
WHERE [t0].[Filter] = @p0
Running speed test (primary key: no)... Queried 40000 records in 0.09 seconds.
@p0:FilterA
SELECT [t0].[VID], [t0].[EID], [t0].[Filter], [t0].[MID], [t0].[SD], [t0].[ED], [t0].[B]
FROM [dbo].[TestHeap] AS [t0]
WHERE [t0].[Filter] = @p0
Clearing database tables...
Generating problematic data...
Saving 120000 rows to table with primary key...
Copying data to table with no primary key...
Done creating test data.
Running speed test (primary key: yes)... Queried 40000 records in 30.46 seconds.
@p0:FilterA
SELECT [t0].[VID], [t0].[EID], [t0].[Filter], [t0].[MID], [t0].[SD], [t0].[ED], [t0].[B]
FROM [dbo].[TestClusteredIndex] AS [t0]
WHERE [t0].[Filter] = @p0
Running speed test (primary key: no)... Queried 40000 records in 0.07 seconds.
@p0:FilterA
SELECT [t0].[VID], [t0].[EID], [t0].[Filter], [t0].[MID], [t0].[SD], [t0].[ED], [t0].[B]
FROM [dbo].[TestHeap] AS [t0]
WHERE [t0].[Filter] = @p0
*/