【发布时间】:2013-08-15 11:17:42
【问题描述】:
我正在考虑将 Sql Server Compact 4.0 用于 Windows 服务应用程序(不是 Web)的数据存储。
我担心多个调用者的服务性能,所以我起草了一个小测试,结果与我的预期完全相反。 (见下面的代码)
测试只是将 500 行添加到一个简单数据库中的表中,该数据库是使用 Entity Framework 5.0 首先使用代码创建的。
它添加行的方式很特别。它调用 AddRow 方法 500 次,并将其留给 AddRow 方法来创建 DbContext 的实例,然后添加行然后处置上下文。我这样做的原因是调用 AddRow 500 次的循环是为了模拟来自客户端的 500 次调用(尽管您可能会说这是不现实的快速连续调用)
为了模拟多个客户端,我启动了一些任务来完成 LoadData 方法的工作。
现在进行观察: 一个线程调用 LoadData,最后调用 AddRow 500 次,在我的工作站上添加 500 行的时间约为 50 秒(每秒只有 10 行!)
有 5 个线程调用 LoadData,我希望每个线程最多在 50 秒内执行,但 5 个线程中有 4 个在 3 秒内完成,最后一个线程在 10 秒内完成。
我假设最后一个线程运行的时间更长,因为 3 秒后其他 4 个线程都完成了,所以最后一个线程单独运行,在这个测试中单独运行似乎会导致灾难性的性能。
我应该注意,如果我从 Sql Compact 更改为 Local DB,那么性能完全符合预期:每个线程的运行时间大致相同,大约 3 秒。
这是完整的代码和 app.config,您应该可以将其剪切并粘贴到新的 Visual Studio 2012 控制台项目中。
目前,我最好的猜测是,在单线程测试中,没有发生连接池,但在其他测试中不知何故存在?这对我来说没有任何意义,我的问题是应该怎么做才能让单个调用者线程的测试运行与多个调用者线程一样快?
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
namespace SqlCompactPerf
{
class Program
{
private const int MaxThreads = 5;
private const int RowsToInsert = 500;
private static void Main()
{
using (var context = new MyContext())
context.Database.Delete();
var tasks = new List<Task>();
for (var threadCount = 1; threadCount <= MaxThreads; threadCount++)
tasks.Add(Task<TimeSpan>.Factory.StartNew(LoadData)
.ContinueWith(x =>
Console.WriteLine("Time: {0}", x.Result)));
Task.WaitAll(tasks.ToArray());
using (var context = new MyContext())
Console.WriteLine("Expecting {0}, Found {1}",
RowsToInsert * MaxThreads,
context.Rows.Count());
}
static TimeSpan LoadData()
{
var watch = new Stopwatch();
watch.Start();
for (var i = 1; i <= RowsToInsert; i++)
{
AddRow(DateTime.Now.ToString(CultureInfo.InvariantCulture));
}
watch.Stop();
return watch.Elapsed;
}
static void AddRow(string data)
{
using (var context = new MyContext())
{
context.Rows.Add(new Row { Id = Guid.NewGuid(), Data = data });
context.SaveChanges();
}
}
}
class MyContext : DbContext
{
public DbSet<Row> Rows { get; set; }
}
internal class Row
{
public Guid Id { get; set; }
public String Data { get; set; }
}
}
这是 App.Config 文件:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlCeConnectionFactory, EntityFramework">
<parameters>
<parameter value="System.Data.SqlServerCe.4.0" />
</parameters>
</defaultConnectionFactory>
</entityFramework>
<!--<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
<parameters>
<parameter value="v11.0" />
</parameters>
</defaultConnectionFactory>
</entityFramework>-->
</configuration>
【问题讨论】:
-
在这个批次上运行 inteelitrace。和/或 SQL 探查器。您可能大部分时间都在加载 ef 模型。这是普通的。但是当加载的东西是“好的”。你可能会发现这很有用msdn.microsoft.com/en-us/data/hh949853
-
我认为我不能在 Sql CE 数据库上运行 SQL Profiler,因为没有可连接的服务器。
-
好的,但 Intellitrace 应该会有所帮助
-
为什么在 AddRow 中使用 new MyContext()?这是很多开销。为什么不只更新一次并持有它?
-
@Blam 通常开销并不大——事实上,在使用 LocalDB 时,尽可能晚地打开和尽可能早地关闭的方法效果很好。其次,这个示例是模拟一个正常的无状态服务器,它从客户端(由一个或多个并发线程表示)接收消息,并且每个客户端发送一个添加行请求。
标签: .net performance entity-framework sql-server-ce