【问题标题】:Performance issue with SQLite database (.db)SQLite 数据库 (.db) 的性能问题
【发布时间】:2014-08-25 08:42:44
【问题描述】:

我遇到了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 GHz4 GB RAM500 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

so 这种 100000 条语句在不同的线程中构建并插入到LIST

最后在上面的代码中执行UPDATE语句(现在使用Larry建议的代码)

【问题讨论】:

  • 实际执行的 SQL 命令是什么?
  • @CL。实际命令在 castarraylist
  • @Larry .. 我认为内存是这里的问题,所以我使用了 GC.Collect()
  • 我没有问它们存储在哪里,而是它们是什么。越来越多的时间表明您在那里有一些非索引查找。显示一些 SQL 示例。
  • 现在是不是更好,没有 GC.Collect,包含整个处理的事务?

标签: c# sql database sqlite spatialite


【解决方案1】:

目前,事务是按查询运行的,这是没有意义的。

将您的主循环代码包含在事务中,并删除此 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();
}

【讨论】:

  • @Larry... 我使用了您的代码,并且将所有值都放在一个列表中。所以我的代码现在是...using (var transaction = con.BeginTransaction()){try{using (var cmd = new SQLiteCommand(con)) {cmd.Transaction = transaction; for (int q = 0; q &lt; latlongquery1output.Count; q++) {cmd.CommandText = latlongquery1output[q]; cmd.ExecuteNonQuery();}} transaction.Commit();} catch{transaction.Rollback();throw;}}
  • @Larry...目前这个列表包含 100 万条记录...并且这个代码是单个记录失败,总交易失败,所以有没有更简单的方法?
  • @Hardik 哦,我明白了。我相应地调整了代码,因此如果记录因任何原因拒绝更新,它不会回滚整个更改。我还更改了 SQLLiteCommand 对象的使用方式,因此不再重复使用它。让我知道它是否更好。
  • @Larry... 是的,您的代码和 Index 的创建对我帮助很大,它也提高了我的表现(现在任务在 仅 5 分钟内完成)...如果记录因任何原因拒绝更新,我们能否更改代码使其无法回滚整个更改?
  • 很高兴它有帮助! :) 实际上,答案中的代码旨在如果单个记录拒绝更新,则不会回滚对数据库的更改。相反,如果更新失败(在文件或其他任何内容中),您将必须在 catch 部分中实现某些内容以记录日志,并且它将一直持续到一切完成为止。然后最终提交所有更新。您是否尝试在新表中读取和插入记录而不是更新现有表?它可能会更快。
【解决方案2】:

首先,您应该尝试使用准备好的语句以获得更好的性能。看一下 System.Data.SQLite 文档,这样你就可以使用SQLiteParameter 并在循环中设置参数值。

其次,ArrayList 应该比 List 或 Array 慢。也许改变它会有所帮助。

第三,可能有一些Pragma commands可以使用。

编辑:我看到你已经关闭了同步和日志模式,我不确定你应该使用任何其他 pragma。在某些情况下,locking_mode = EXCLUSIVE 和 temp_store = MEMORY 会有所帮助。

【讨论】:

  • @Griddor... 我没有传递任何参数值。我是直接在另一个线程中预先声明语句...我尝试使用LIST,但它并没有提高太多性能.. 是的,我在连接数据库时使用的所有 Pragma 命令都是众所周知的
  • @Hardik 实际上prepared statement是使用SQLite的重要部分,只是在循环中改变参数值。直接执行查询意味着为每个单独的查询准备语句。此外,就像拉里的回答一样,在所有循环之后只提交一次事务应该比多次提交更快。
【解决方案3】:

SQLite 可能没有性能问题;您几乎可以肯定自己的代码存在性能问题:

  • 几乎可以肯定根本不需要调用 GC.Collect()。你在这里所做的不应该造成任何显着的内存压力,如果是这样,我强烈建议让垃圾收集器自己做它自己的事情,而不是强迫这个问题。更糟糕的是,您在循环的每一次迭代上都调用了 GC.Collect()。不要这样做!

  • 真的有必要在自己的事务中进行每个单独的更新吗?您确实意识到,如果您的代码失败并在此循环的中途抛出异常,则前半部分更新将已提交,但您将无法从中断的地方继续?您甚至无法轻松知道您离开的位置。

  • 您使用 ArrayList 而不是 List 有什么特别的原因吗?这导致您需要在内部循环中执行强制转换并调用 Convert.ToString,这不是必需的(除非您有非常非常好的理由使用 ArrayList)。

【讨论】:

    【解决方案4】:

    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;
    

    【讨论】:

    • @CL... 是的,我已经创建了 INDEX,它真的非常有效。
    • 但我认为我无法创建主键,因为我有时会插入超过 1 列并且它们不是预定义的。
    • 当前面临的问题...更新语句我在不同的线程中创建,它在字符串和数组上给出 outofMemory 异常。
    猜你喜欢
    • 2012-10-07
    • 2013-05-06
    • 2017-04-21
    • 2012-11-21
    • 2022-01-03
    • 1970-01-01
    • 2017-10-10
    • 2014-07-11
    • 2011-05-15
    相关资源
    最近更新 更多