如何提高 ADO.NET 中大量 INSERT 的速度?

Posted

技术标签:

【中文标题】如何提高 ADO.NET 中大量 INSERT 的速度?【英文标题】:How can I enhance the speed of massive INSERT in ADO.NET? 【发布时间】:2011-01-09 17:30:35 【问题描述】:

我下载了 Advanced Database Server (ADS) 10.1 的试用版以及 ARC 和 ADO.NET 提供程序。与 SQLite .NET (http://sqlite.phxsoftware.com/) 相比,我的主要目的是从数百万条记录中了解大量 INSERTS 的性能。

在 ADS 中,30 分钟内加载了 700 万条条目。

在 Sqlite for.NET 中,同样的 700 万个条目在不到 3 分钟的时间内被加载!!

为什么?我可以做些什么来提高 .NET 提供程序的 ADS 速度?

问候。

编辑

感谢您的建议,在 ADS 代码中我错误地包含了一些索引的创建,当我取消这个时,加载经过的时间是 10 分钟。

让我添加一些示例代码和示例数据(您可以将它们相乘直到获得 700 万个条目)。如果您能找到增强和优化性能的方法,请告诉我。

SYBASE 广告代码:

 public void LoadAds(string opt, string file)
    
        AdsConnection conn = new AdsConnection(@"data source=C:\apps\dataApps\cmpExistenc\inv.ads;" +
            "ServerType=local; TableType=ADT");
        conn.Open();
        var stV = new st();
        var dicTxt = new Dictionary<object, st>();
        if (opt.ToUpper() == "C")
        
            using (AdsCommand cmd = conn.CreateCommand())
            
                try
                
                    cmd.CommandText = "DROP TABLE lbl; DROP TABLE almlbl";
                    cmd.ExecuteNonQuery();
                
                catch  
                cmd.CommandText =
                    "CREATE TABLE lbl (alm varchar(4), alm2 varchar(4), " +
                       "mat varchar(18), ser varchar(20), fac varchar(18), almlbl varchar(10), " +
                       "cant integer, sts varchar(1), ser_2_20 varchar(20), rowid_sap integer, stsmat varchar(100));";
                cmd.ExecuteNonQuery();
                cmd.CommandText =
                    "CREATE TABLE almlbl (almlbl varchar(10), almlbltxt varchar(100), " +
                       "ciudad varchar(50));";
                cmd.ExecuteNonQuery();
            
        
        else
            using (AdsCommand cmdTxt = conn.CreateCommand())
            
                cmdTxt.CommandText = "SELECT * from almlbl";
                AdsDataReader drT = cmdTxt.ExecuteReader();
                while (drT.Read())
                    dicTxt[drT[0]] = new st()  almlblTxt = drT[1], ciudad = drT[2] ;
                drT.Dispose();
                cmdTxt.CommandText = "DELETE FROM almlbl";
                cmdTxt.ExecuteNonQuery();
            


        using (AdsTransaction transac = conn.BeginTransaction())
        
            AdsCommand cmd = conn.CreateCommand();
            cmd.Transaction = transac;
            AdsParameter param = cmd.CreateParameter();
            cmd = LibCorp.Ads.buildParmsFromTable("lbl", conn, transac);
            long regLei = 0;
            List<object> cols;
            try
            
                StreamReader sr = new StreamReader(file, Encoding.Default);
                sr.ReadLine(); // Ignore title
                string line;
                while ((line = sr.ReadLine()) != null)
                
                    if ((++regLei % 1000000) == 0)
                        o.show(string.Format(" lbl:0", regLei), tbx);
                    cols = new List<object>(line.Split('|'));
                    if (!dicTxt.ContainsKey((string)cols[5]))
                    
                        stV.almlblTxt = cols[6];
                        stV.ciudad = cols[8];
                        dicTxt[cols[5]] = stV;
                    
                    if (!cols[3].Equals("") && cols[3].ToString().Length > 18)
                        cols.Add(cols[3].ToString().Substring(1));
                    else
                        cols.Add(DBNull.Value);

                    cmd.Parameters[0].Value = cols[0];       // alm
                    cmd.Parameters[1].Value = cols[1];       // alm2
                    cmd.Parameters[2].Value = cols[2];       // mat
                    cmd.Parameters[3].Value = cols[3];       // ser
                    cmd.Parameters[4].Value = cols[4];       // fac
                    cmd.Parameters[5].Value = cols[5];       // almlbl
                    cmd.Parameters[6].Value = cols[7];       // cant
                    cmd.Parameters[7].Value = DBNull.Value;  // sts
                    cmd.Parameters[8].Value = cols[10];      // ser_2_20
                    cmd.Parameters[9].Value = DBNull.Value;  // rowid_sap
                    cmd.Parameters[10].Value = cols[9];      // stsmat
                    cmd.ExecuteNonQuery();
                

                foreach (KeyValuePair<object, st> pair in dicTxt)
                
                    cmd.CommandText = string.Format("INSERT INTO almlbl VALUES('0','1','2')",
                            pair.Key, pair.Value.almlblTxt, pair.Value.ciudad);
                    cmd.ExecuteNonQuery();
                

                transac.Commit();
            
            catch (Exception ex)
            
                o.notify(string.Format("0\n\rSitio->1", ex, ex.TargetSite.Name));
            
            finally
            
                conn.Close();
            

        
    

.NET 的 SQLITE 代码:

public void LoadSQLITE(string opt, string file)
    
        conn = new SQLiteConnection
            (@"Data Source=inv.db3; Page Size=65536; Cache Size=65536; Synchronous=Off; Journal Mode=Off;");
        conn.Open();
        var stV = new st();
        var dicTxt = new Dictionary<object, st>(); // faster than SortedDictionary
        if (opt.ToUpper() == "C")
        
            using (SQLiteCommand cmd = new SQLiteCommand(conn))
            
                try
                
                    cmd.CommandText = "DROP TABLE lbl; DROP TABLE almlbl";
                    cmd.ExecuteNonQuery();
                
                catch  
                cmd.CommandText =
                    "CREATE TABLE lbl (alm varchar(4), alm2 varchar(4), " +
                       "mat varchar(18), ser varchar(20), fac varchar(18), almlbl varchar(10), " +
                       "cant integer, sts varchar(1), ser_2_20 varchar(20), rowid_sap integer, stsmat varchar);" +
                    "CREATE TABLE almlbl (almlbl varchar(10), almlbltxt varchar(100), " +
                       "ciudad varchar(50));"; //+
                cmd.ExecuteNonQuery();
            
        
        else
            using (SQLiteCommand cmdTxt = new SQLiteCommand(conn))
            
                cmdTxt.CommandText = "SELECT * from almlbl";
                SQLiteDataReader drT = cmdTxt.ExecuteReader();
                while (drT.Read())
                    dicTxt[drT[0]] = new st()  almlblTxt = drT[1], ciudad = drT[2] ;
                drT.Dispose();
                cmdTxt.CommandText = "DELETE FROM almlbl";
                cmdTxt.ExecuteNonQuery();
            

        using (SQLiteTransaction transac = conn.BeginTransaction())
        
            using (SQLiteCommand cmd = new SQLiteCommand(conn))
            
                SQLiteParameter param = new SQLiteParameter();
                SQLiteCommand cmdAux = LibCorp.Lite.buildParmsFromTable("lbl", conn);
                cmd.CommandText = cmdAux.CommandText;
                foreach (SQLiteParameter sp in cmdAux.Parameters)
                    cmd.Parameters.Add(sp);
                long regLei = 0;
                try
                
                    StreamReader sr = new StreamReader(file, Encoding.Default);
                    sr.ReadLine(); // Ignore title
                    List<object> cols;
                    string line;
                    while (!string.Equals(line = sr.ReadLine(), null)) // Fastest way
                    
                        if ((++regLei % 1000000) == 0)  //Diff of only 1 or 2 secs if omitted
                            o.show(string.Format(" lbl:0", regLei), tbx);
                        cols = new List<object>(line.Split('|')); // Fastest way
                        if (!dicTxt.ContainsKey((string)cols[5])) // diff of only 1 sec if commented
                        
                            stV.almlblTxt = cols[6];
                            stV.ciudad = cols[8];
                            dicTxt[cols[5]] = stV;
                        

                        if (!cols[3].Equals("") && cols[3].ToString().Length > 18)
                            cols.Add(cols[3].ToString().Substring(1));
                        else
                            cols.Add(DBNull.Value);

                        cmd.Parameters[0].Value = cols[0];       // alm
                        cmd.Parameters[1].Value = cols[1];       // alm2
                        cmd.Parameters[2].Value = cols[2];       // mat
                        cmd.Parameters[3].Value = cols[3];       // ser
                        cmd.Parameters[4].Value = cols[4];       // fac
                        cmd.Parameters[5].Value = cols[5];       // almlbl
                        cmd.Parameters[6].Value = cols[7];       // cant
                        cmd.Parameters[7].Value = DBNull.Value;  // sts
                        cmd.Parameters[8].Value = cols[10];      // ser_2_20
                        cmd.Parameters[9].Value = DBNull.Value;  // rowid_sap
                        cmd.Parameters[10].Value = cols[9];      // stsmat
                        cmd.ExecuteNonQuery();
                    

                    foreach (KeyValuePair<object, st> pair in dicTxt)
                    
                        cmd.CommandText = string.Format("INSERT INTO almlbl VALUES('0','1','2')",
                                pair.Key, pair.Value.almlblTxt, pair.Value.ciudad);
                        cmd.ExecuteNonQuery();
                    

                    transac.Commit();
                
                catch (Exception ex)
                
                    o.notify(string.Format("0\n\rSitio->1", ex, ex.TargetSite.Name));
                
                finally
                
                    conn.Close();
                
            

        
    

以及一些用于测试的数据(乘以 700 万,请包括标题行):

COD_ALMACEN_SAP|COD_ALMACEN_SAP2|CODIGO_SAP|NRO_SERIE_INICIAL|NRO_INICIO_FACTURA|COD_ALMACEN|NOMBRE_ALMACEN|CANTIDAD|CIUDAD|NOMBRE_ARTICULO

1030|8030|ADAJKIUSD66K|||16|ALMACEN DANSA PRUZ TESULARES|4|BANDA PRUZ|ADA-KI-SD66K ADAPTADOR MAIDIUM SD-66K 1030|8030|BAT-KI-BPA101|||16|ALMACEN LANTA CRUZ TESULARES|5|BANDA PRUZ|BAT-KI-BPA101 BATTERIESAPS 1020|8020|TARGESA/P_PC-GC79|||17|PERRITORIAL CENTER|9|POCHASALTA|TARGESA/P_PC-GC79 TARGESA UNIVERSAL P PC GPRS Y WL 1010|8010|TARJETA/P_PC-GC79|||1014|TERRITORIES NORTH 1|5|PATPAZ|TARGESA/P_PC-GC79 TARJETA UNIVERSAL P PC GPRS Y WL 1060|8060|TARJETA/P_PC-GC79|||1095|ALMACEN SUNY|1|TRONOSAD|TARGESA/P_PC-GC79 TARGESA UNIVERSAL P PC GPRS Y WL

enter code here

问候。

问候。

【问题讨论】:

你好朱丽叶,我发布了代码。 值得尝试的一件事是限制事务的大小。我见过一个例子,其中一个巨大的变化是如此之大,以至于需要很长时间才能在事务日志中写入一个巨大的条目。 (不作为答案发布,因为这是道听途说)。 Jan,非常感谢您的 cmets,因为您可以看到 Sqlite 和 ADS 受制于相同的代码条件,但 Sqlite 是迄今为止最快的(至少在批量插入中)。我想知道如何在我的程序中使用计数器来限制交易的大小?问候。 【参考方案1】:

首先确保在这两种情况下您使用的事务和查询是相同的。第二确保您以相同的方式调用 Insert 查询 - 不要在一种情况下创建冗余(对于每个 Insert 调用)连接、查询或适配器对象,但在另一种情况下不创建。差别太……显着了。

【讨论】:

也就是说,#t SQL Lite 不是嵌入的,而是服务器(这意味着调用通过网络层)?我建议在 10-20 个线程上并行插入。 您好 TomTom,我配置了 20 个线程,但没有重要的改进,平均每插入 100 万个条目的时间保持在 2 分钟。 UGEEN,我包含了 Sybase 和 .NET Sqlite 的代码;我在 PC 上运行的代码非常相似,但 .NET Sqlite 比 Sybase 更快。【参考方案2】:

尝试删除交易。 ADS 事务不同于传统的 RDBMS,需要更多的操作系统刷新操作(ADS 不使用检查点)。如果没有事务,性能会更好。

编辑 注意到您使用的是本地服务器,因此我关于交易的 cmets 将不相关。该死!

另外,我不知道第二个循环 (INSERT INTO almlbl) 中涉及多少个循环,但是使用参数更改为准备好的查询会有所帮助。

很可能所有这些 SQLite 行都不在磁盘上。我会假设有很多在内存中并且没有被刷新。

【讨论】:

杰里米,非常感谢您的回答,与 lbl 相比,表 almlbl 非常非常小。如您所见,Sqlite 和 ADS 在相同的代码条件下加载相同数量的记录。我想 ADS 试图在不丢失数据方面保持安全,因此加载时间要长一些(比 Sqlite 多 5 到 6 分钟内有 700 万条记录),但是我从来没有遇到过 Sqlite 问题。问候。

以上是关于如何提高 ADO.NET 中大量 INSERT 的速度?的主要内容,如果未能解决你的问题,请参考以下文章

使用ado.net 如何获取新添记录的自增id值?

使用 INSERT IDENTITY 插入多个表 - ADO.Net

ADO.NET 执行部分更新/插入

oracle 大量数据insert操作怎么提高效率

Ado.net 断开更新整个表

在带有事务“并发冲突”的 Ado.Net 更新中