我正在使用C#导入一个包含6-8百万行的CSV。
我的表格如下:
CREATE TABLE [Data] ([ID] VARCHAR(100) NULL,[Raw] VARCHAR(200) NULL)
CREATE INDEX IDLookup ON Data(ID ASC)
我正在使用System.Data.SQLite进行导入。
目前在Windows 7 32bit,Core2Duo 2.8Ghz& 4GB RAM。这不是太糟糕,但我只是想知道是否有人能够更快地看到导入它的方式。
这是我的代码:
public class Data
{
public string IDData { get; set; }
public string RawData { get; set; }
}
string connectionString = @"Data Source=" + Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory + "\\dbimport");
System.Data.SQLite.SQLiteConnection conn = new System.Data.SQLite.SQLiteConnection(connectionString);
conn.Open();
//Dropping and recreating the table seems to be the quickest way to get old data removed
System.Data.SQLite.SQLiteCommand command = new System.Data.SQLite.SQLiteCommand(conn);
command.CommandText = "DROP TABLE Data";
command.ExecuteNonQuery();
command.CommandText = @"CREATE TABLE [Data] ([ID] VARCHAR(100) NULL,[Raw] VARCHAR(200) NULL)";
command.ExecuteNonQuery();
command.CommandText = "CREATE INDEX IDLookup ON Data(ID ASC)";
command.ExecuteNonQuery();
string insertText = "INSERT INTO Data (ID,RAW) VALUES(@P0,@P1)";
SQLiteTransaction trans = conn.BeginTransaction();
command.Transaction = trans;
command.CommandText = insertText;
Stopwatch sw = new Stopwatch();
sw.Start();
using (CsvReader csv = new CsvReader(new StreamReader(@"C:\Data.txt"), false))
{
var f = csv.Select(x => new Data() { IDData = x[27], RawData = String.Join(",", x.Take(24)) });
foreach (var item in f)
{
command.Parameters.AddWithValue("@P0", item.IDData);
command.Parameters.AddWithValue("@P1", item.RawData);
command.ExecuteNonQuery();
}
}
trans.Commit();
sw.Stop();
Debug.WriteLine(sw.Elapsed.Minutes + "Min(s) " + sw.Elapsed.Seconds + "Sec(s)");
conn.Close();
答案 0 :(得分:11)
对于600万条记录而言,这是非常快的。
看来你正在以正确的方式做到这一点,前段时间我在sqlite.org上看到,在插入记录时你需要将这些插入放在事务中,如果你不这样做,你的插入将受到限制每秒只有60!这是因为每个插入都将被视为一个单独的事务,每个事务必须等待磁盘完全旋转。你可以在这里阅读完整的解释:
http://www.sqlite.org/faq.html#q19
实际上,SQLite很容易在普通台式计算机上每秒执行50,000或更多INSERT语句。但它每秒只会进行几十次交易。交易速度受磁盘驱动器转速的限制。事务通常需要两个完整的磁盘盘旋转,这在7200RPM磁盘驱动器上限制你每秒约60个事务。
比较您的时间与上述平均值:每秒50,000; =>应该花费2分00秒。这比你的时间快一点。
事务速度受磁盘驱动器速度的限制,因为(默认情况下)SQLite实际上等待,直到事务完成之前数据确实安全地存储在磁盘表面上。这样,如果您突然断电或者操作系统崩溃,您的数据仍然是安全的。有关详细信息,请阅读SQLite中的原子提交..
默认情况下,每个INSERT语句都是自己的事务。但是如果用BEGIN ... COMMIT包围多个INSERT语句,则所有插入都被分组到一个事务中。提交事务所需的时间在所有随附的insert语句中分摊,因此每个insert语句的时间大大减少。
下一段中有一些暗示可以尝试加速插入:
另一种选择是运行PRAGMA synchronous = OFF。此命令将导致SQLite不等待数据到达磁盘表面,这将使写入操作看起来更快。但是如果你在事务中断电,你的数据库文件可能会损坏。
我一直以为SQLite是为“简单的东西”而设计的,在我看来,600万条记录对于像MySQL这样的真实数据库服务器来说是一项工作。
使用如此多的记录计算SQLite表中的记录可能需要很长时间,仅为了您的信息,而不是使用SELECT COUNT(*),您总是可以使用非常快的SELECT MAX(rowid),但不是如果您要删除该表中的记录,那么准确无误。
EDIT。
正如Mike Woodhouse所说,在插入记录后创建索引应该加速整个事情,这是其他数据库中的常见建议,但不能肯定地说它在SQLite中是如何工作的。
答案 1 :(得分:5)
您可能尝试的一件事是在插入数据后创建索引 - 通常,数据库在单个操作中构建索引要比在每次插入(或事务)后更新它快得多)。
我不能说它肯定会与SQLite一起使用,但因为它只需要两行来移动它值得尝试。
我也想知道600万行事务是否会走得太远 - 您是否可以更改代码以尝试不同的事务大小?说100,1000,10000,100000?有没有“甜蜜点”?
答案 2 :(得分:1)
以下列方式绑定参数可以获得相当长的时间:
...
string insertText = "INSERT INTO Data (ID,RAW) VALUES( ? , ? )"; // (1)
SQLiteTransaction trans = conn.BeginTransaction();
command.Transaction = trans;
command.CommandText = insertText;
//(2)------
SQLiteParameter p0 = new SQLiteParameter();
SQLiteParameter p1 = new SQLiteParameter();
command.Parameters.Add(p0);
command.Parameters.Add(p1);
//---------
Stopwatch sw = new Stopwatch();
sw.Start();
using (CsvReader csv = new CsvReader(new StreamReader(@"C:\Data.txt"), false))
{
var f = csv.Select(x => new Data() { IDData = x[27], RawData = String.Join(",", x.Take(24)) });
foreach (var item in f)
{
//(3)--------
p0.Value = item.IDData;
p1.Value = item.RawData;
//-----------
command.ExecuteNonQuery();
}
}
trans.Commit();
...
在第1,2和3部分进行更改。 通过这种方式,参数绑定似乎要快得多。 特别是当你有很多参数时,这种方法可以节省很多时间。
答案 3 :(得分:0)
我做了类似的导入,但我让我的c#代码首先将数据写入csv,然后运行sqlite import实用程序。我可以用这种方式在大约10分钟内输入超过3亿条记录。
不确定是否可以直接从c#进行此操作。