历史
我有一个"记录"(3500(列表,我将其保存到XML中,并在程序退出时压缩。自:
- 记录的数量增加
- 只有大约50条记录需要在退出时更新
- 节省大约需要3秒
我需要另一个解决方案——嵌入式数据库。我选择SQL CE是因为它可以毫无问题地使用VS,而且许可证对我来说还可以(我将其与Firebird、SQLite和EffiProz以及BerkeleyDB进行了比较(。
数据
记录结构:11个字段,其中2个为主键(nvarchar+byte(。其他记录包括字节、数据时间、double和int。
我不使用任何关系、联接、索引(主键除外(、触发器、视图等。它实际上是一个平面字典——成对的key+Value。我修改了其中的一些,然后我必须在数据库中更新它们。我不时添加一些新的"记录",并需要存储(插入(它们。仅此而已。
LINQ方法
我有一个空白的数据库(文件(,所以我在一个循环中(一个接一个(插入3500个。我甚至不检查记录是否已经存在,因为数据库是空白的。
执行时间?4分52秒。我晕倒了(请注意:XML+压缩=3秒(。
SQL CE原始方法
我在谷歌上搜索了一下,尽管有这样的说法:LINQ to SQL(CE(速度与SqlCe声明这是SQL CE本身的错误,我尝试了一下。
相同的循环,但这次插入是使用SqlCeResultSet(DirectTable模式,请参阅:SQL Server CE中的大容量插入(和SqlCeUpdatableRecord进行的。
结果如何?你坐得舒服吗?好0.3秒(是的,是秒的零头!(。
问题
LINQ的可读性非常强,而原始操作恰恰相反。我可以编写一个映射器,将所有列索引转换为有意义的名称,但这似乎是在重新发明轮子——毕竟它已经在…LINQ中完成了。
所以,也许这是告诉LINQ加快速度的一种方式问题--怎么做?
代码
LINQ
foreach (var entry in dict.Entries.Where(it => it.AlteredByLearning))
{
PrimLibrary.Database.Progress record = null;
record = new PrimLibrary.Database.Progress();
record.Text = entry.Text;
record.Direction = (byte)entry.dir;
db.Progress.InsertOnSubmit(record);
record.Status = (byte)entry.LastLearningInfo.status.Value;
// ... and so on
db.SubmitChanges();
}
原始操作
SqlCeCommand cmd=conn.CreateCommand((;
cmd.CommandText="进度";cmd。CommandType=System.Data.CommandType.TableDirect;SqlCeResultSet rs=cmd。ExecuteResultSet(ResultSetOptions.Updataable(;
foreach (var entry in dict.Entries.Where(it => it.AlteredByLearning))
{
SqlCeUpdatableRecord record = null;
record = rs.CreateRecord();
int col = 0;
record.SetString(col++, entry.Text);
record.SetByte(col++,(byte)entry.dir);
record.SetByte(col++,(byte)entry.LastLearningInfo.status.Value);
// ... and so on
rs.Insert(record);
}
每个事务做更多的工作
对于典型的关系数据库来说,提交通常是非常昂贵的操作,因为数据库必须等待磁盘刷新以确保数据不会丢失(ACID保证等等(。在这种操作中,没有专用控制器的传统HDD磁盘IO非常慢:数据必须刷新到物理磁盘——之间的IO同步可能每秒只能进行30-60次提交
请参阅SQLite常见问题解答:INSERT真的很慢——我每秒只能执行几十次INSERT。忽略不同的数据库引擎,这是完全相同的问题。
通常,LINQ2SQL在SubmitChanges
内创建一个新的隐式事务。为了避免这种隐式事务/提交(提交是昂贵的操作(,请执行以下操作之一:
-
少调用
SubmitChanges
(例如,在循环外部一次(或; -
设置显式事务范围(请参阅TransactionScope(。
使用较大事务上下文的一个示例是:
using (var ts = new TransactionScope()) {
// LINQ2SQL will automatically enlist in the transaction scope.
// SubmitChanges now will NOT create a new transaction/commit each time.
DoImportStuffThatRunsWithinASingleTransaction();
// Important: Make sure to COMMIT the transaction.
// (The transaction used for SubmitChanges is committed to the DB.)
// This is when the disk sync actually has to happen,
// but it only happens once, not 3500 times!
ts.Complete();
}
然而,使用单个事务或对SubmitChanges的单个调用的方法的语义与上面调用SubmitChanges3500次并创建3500个不同的隐式事务的代码的语义不同。特别地,原子操作的大小(相对于数据库(是不同的,并且可能不适合所有任务。
对于LINQ2SQL更新,更改乐观并发模型(例如,禁用它或仅使用时间戳字段(可能会带来较小的性能改进。然而,最大的改进将来自于减少必须执行的提交数量。
快乐的编码。
我对此并不肯定,但似乎db.SubmitChanges()
调用应该在循环之外进行。也许这会加快速度?