我遇到SQLite database (.db)
我正在尝试更新数据库(.db)中的1,00,000条记录,大约需要50分钟。太慢了。
我的代码如下::
for (int q = 0; q < list.Count; q++)
{
ArrayList castarraylist = new ArrayList();
castarraylist = (ArrayList)(list[q]);
using (var cmd = new SQLiteCommand(con))
using (var transaction = con.BeginTransaction())
{
cmd.Transaction = transaction;
for (int y = 0; y < castarraylist.Count; y++)
{
cmd.CommandText = Convert.ToString(castarraylist[y]);
cmd.ExecuteNonQuery();
}
transaction.Commit();
GC.Collect();
}
}
这里每个castarraylist包含5000条记录。用事务更新到数据库。所以循环经历20次并完成所有更新。 当我手动检查时间时,它会增加每次迭代5000次记录的时间。喜欢
1st 5000 records processing time > 1:11 minute
2nd 5000 records processing time > 1:25 minute
3rd 5000 records processing time > 1:32 minute
4th 5000 records processing time > 1:40 minute
5th 5000 records processing time > 1:47 minute
6th 5000 records processing time > 1:52 minute
...
...
...
17th 5000 records processing time > 3:32 minute
18th 5000 records processing time > 3:44 minute
19th 5000 records processing time > 4:02 minute
20th 5000 records processing time> 4:56 minute
为什么会发生这种情况我无法理解。
我用C#和笔记本电脑配置编写的源代码是i5 2.6 GHz
,4 GB RAM
,500 GB HD
。
我在下面做了连接::
SQLiteConnection con = new SQLiteConnection("Data Source=" + fullPath + ";Version=3;Count Changes=off;Journal Mode=off;Pooling=true;Cache Size=10000;Page Size=4096;Synchronous=off");
(* fullpath - 是我的数据库路径)
我正在创建如下表格......
sqlquery2="Select LINK_ID from RDF_LINK
string createLinkToPoly = "create table temp2 AS " + sqlquery2;
这将创建一个表并插入由sqlquery2传递的记录。
下面的语句在SQLite上扩展了Spatialite
ExecuteStatement("select load_extension('spatialite.dll')", con);
我的Update
声明如下::
UPDATE temp2 SET GEOM = Transform(LineStringFromText('LINESTRING(4.38368 51.18109,4.38427 51.18165)',4326),32632)WHERE LINK_ID= 53841546
所以这种100000语句构建在不同的线程中并插入LIST
最后在上面的代码中执行UPDATE
语句(现在使用Larry建议的代码)
答案 0 :(得分:3)
首先,您应该尝试使用预准备语句以获得更好的性能。看一下System.Data.SQLite文档,这样就可以使用SQLiteParameter
并在循环中设置参数值。
其次,ArrayList
应该比List
或数组慢。也许改变可以帮助。
第三,可以使用一些Pragma commands。
编辑:我看到你已经关闭了同步和journal_mode,我不确定你应该使用其他任何编译指示。在某些情况下,locking_mode = EXCLUSIVE和temp_store = MEMORY可能会有所帮助。
答案 1 :(得分:3)
Currencly,事务是按查询运行的,这没有任何意义。
将主循环代码包含在事务中,并删除此GC.Collect()。
修改强>
据我所知,您不希望在发生错误时回滚全局更新。所以我稍微改了一下代码。
此外,我不确定通过更改CommandText并再次运行查询可以重用命令对象。这就是为什么我建议每次创建它。
using (var transaction = con.BeginTransaction())
{
for (int q = 0; q < list.Count; q++)
{
var castarraylist = (ArrayList)(list[q]);
for (int y = 0; y < castarraylist.Count; y++)
{
using (var cmd = new SQLiteCommand(con))
{
cmd.Transaction = transaction;
cmd.CommandText = Convert.ToString(castarraylist[y]);
try
{
cmd.ExecuteNonQuery();
}
catch(Exception ex)
{
// Log the update problem
Console.WriteLine("Update problem " + cmd.CommandText + " - Reason: " + ex.Message);
}
}
}
}
transaction.Commit();
}
答案 2 :(得分:2)
您可能没有SQLite的性能问题;您几乎肯定会遇到自己代码的性能问题:
几乎可以肯定没有必要调用GC.Collect()。你在这里做的不应该造成任何重大的内存压力,如果是这样的话,我强烈建议让垃圾收集者自己动手而不是强迫这个问题。更糟糕的是,您在循环的每次迭代上调用GC.Collect()。不要这样做!
是否真的有必要在自己的交易中进行每次更新?你确实意识到,如果你的代码失败并在这个循环中途抛出一个异常,那么前半部分的更新将会被提交,但是你还没有办法从你离开的地方开始接收?你甚至没有一个简单的方法知道你离开的地方。
您是否使用ArrayList而不是List&lt; T&gt;?这导致您需要执行强制转换并在内循环中调用Convert.ToString,这不是必需的(除非您有非常非常好的理由使用ArrayList)。
答案 3 :(得分:2)
UPDATE语句很慢,因为数据库必须扫描表中的所有记录才能找到任何匹配的LINK_ID值。 您需要LINK_ID列上的索引。
在进行更新之前手动创建它:
CREATE INDEX temp2_linkid ON temp2(LINK_ID);
或者在创建表时创建索引(这要求明确创建表):
CREATE TABLE temp2 ( LINK_ID INTEGER PRIMARY KEY );
INSERT INTO temp2(LINK_ID) SELECT LINK_ID FROM RDF_LINK;