使用 C# 和 Npgsql 快速插入 parent 和 child 表
Fast insert into parent and child tables using C# and Npgsql
我正在使用 C#.NET 4.0 (Visual Studio 2010)、PostgreSQL 9.2 和 Npgsql 2.0.12。我无法升级到 Npgsql 3.
我需要快速插入 parent table,然后使用该插入中的主键快速插入 child table.
parent table 有一个定义为 "serial" 的列,它是主键。
child table 有一个整数列,它是返回 parent table 的外键。
并非每个 parent 记录都会有 children。一个 parent 可以有 0 个、1 个或多个 children.
目前我正在将 parent object 缓冲到列表中。当缓冲了 5000 parents 时,从线程池中产生一个新线程将记录写入数据库。 (创建了一个新列表
主线程缓冲下一组 parent objects。)
新线程调用 NpgsqlConnection.BeginTransaction(),然后在循环内调用带参数的 NpgsqlCommand.ExecuteScalar() 以插入 parent 记录并取回主键。
然后构建 parent 的 child object(如果有)并保存到另一个列表。在循环结束时提交 parents 的事务。但是这种方法非常缓慢。插入 5000 条记录需要 3 到 10 秒的时间。当然还有更好的方法。
提交 parent 后,我使用 http://codebetter.com/karlseguin/2009/10/25/postgresql-day-2/ 中描述的 BulkCopy(使用 NpgsqlCopyIn)插入 child 记录。这太棒了。它在不到半秒的时间内插入数千条 child 条记录。
我也喜欢将 BulkCopy 用于 parent 记录。但是我不知道如何从批量插入中取回主键值。
那么使用 C# 和 Npgsql 快速插入 parent 和 child 记录的技巧是什么?答案可能就在某处,但显然我没有使用正确的搜索引擎参数。
非常感谢。
这种情况的答案通常是"hi-lo" key generation。简而言之,这意味着您可以预先分配大量 ID 并在插入时指定它们,而不是让数据库在每次插入时都生成 ID(强制您检索这些 ID)。这意味着您要自己为每个 parent 设置 ID,而不是将其留空(让 PostgreSQL 这样做)。
具体来说,您将从管理 parent table 的 ID 的序列中检索一批 ID - 有关详细信息,请参阅 this question and this article。然后,在您的应用程序中获得 ID 后,您将批量插入具有这些 ID 的 parent。
我会将 parents 插入脚本写入磁盘的文本文件中,然后 运行 它通过常规命令在一次往返中取回所有 parents 主键数据库。
当您使用 serial
数据类型时,Postgres 会自动生成并分配一个序列。这很好,因为您可以劫持该序列用于其他目的,包括这个。
这是我的建议。
假设您的 objects 看起来像这样:
public Parent
{
public long Id { get; set; }
public string Description { get; set; }
public List<Child> Children { get; set; }
}
public Child
{
public long Id { get; set; }
public long ParentId { get; set; }
public string Description { get; set; }
}
让您的代码根据序列为每个 Parent 分配一个 ID。这应该在眨眼之间发生:
NpgsqlCommand cmd = new NpgsqlCommand("select nextval('schema.foo_id_seq')", conn);
foreach (Parent p in parentList.Where(x => x.Id == null && x.Id == 0))
{
p.Id = Convert.ToInt64(cmd.ExecuteScalar());
p.Children.ForEach(x => x.ParentId = p.Id);
}
如果这些记录尚不存在,Where
子句可能并不重要...只是需要考虑一下。
从这里开始,您的 NpgsqlCopyIn
应该会在 parents 和 children 中表现出色。
我正在使用 C#.NET 4.0 (Visual Studio 2010)、PostgreSQL 9.2 和 Npgsql 2.0.12。我无法升级到 Npgsql 3.
我需要快速插入 parent table,然后使用该插入中的主键快速插入 child table.
parent table 有一个定义为 "serial" 的列,它是主键。
child table 有一个整数列,它是返回 parent table 的外键。
并非每个 parent 记录都会有 children。一个 parent 可以有 0 个、1 个或多个 children.
目前我正在将 parent object 缓冲到列表中。当缓冲了 5000 parents 时,从线程池中产生一个新线程将记录写入数据库。 (创建了一个新列表 主线程缓冲下一组 parent objects。) 新线程调用 NpgsqlConnection.BeginTransaction(),然后在循环内调用带参数的 NpgsqlCommand.ExecuteScalar() 以插入 parent 记录并取回主键。 然后构建 parent 的 child object(如果有)并保存到另一个列表。在循环结束时提交 parents 的事务。但是这种方法非常缓慢。插入 5000 条记录需要 3 到 10 秒的时间。当然还有更好的方法。
提交 parent 后,我使用 http://codebetter.com/karlseguin/2009/10/25/postgresql-day-2/ 中描述的 BulkCopy(使用 NpgsqlCopyIn)插入 child 记录。这太棒了。它在不到半秒的时间内插入数千条 child 条记录。
我也喜欢将 BulkCopy 用于 parent 记录。但是我不知道如何从批量插入中取回主键值。
那么使用 C# 和 Npgsql 快速插入 parent 和 child 记录的技巧是什么?答案可能就在某处,但显然我没有使用正确的搜索引擎参数。
非常感谢。
这种情况的答案通常是"hi-lo" key generation。简而言之,这意味着您可以预先分配大量 ID 并在插入时指定它们,而不是让数据库在每次插入时都生成 ID(强制您检索这些 ID)。这意味着您要自己为每个 parent 设置 ID,而不是将其留空(让 PostgreSQL 这样做)。
具体来说,您将从管理 parent table 的 ID 的序列中检索一批 ID - 有关详细信息,请参阅 this question and this article。然后,在您的应用程序中获得 ID 后,您将批量插入具有这些 ID 的 parent。
我会将 parents 插入脚本写入磁盘的文本文件中,然后 运行 它通过常规命令在一次往返中取回所有 parents 主键数据库。
当您使用 serial
数据类型时,Postgres 会自动生成并分配一个序列。这很好,因为您可以劫持该序列用于其他目的,包括这个。
这是我的建议。
假设您的 objects 看起来像这样:
public Parent
{
public long Id { get; set; }
public string Description { get; set; }
public List<Child> Children { get; set; }
}
public Child
{
public long Id { get; set; }
public long ParentId { get; set; }
public string Description { get; set; }
}
让您的代码根据序列为每个 Parent 分配一个 ID。这应该在眨眼之间发生:
NpgsqlCommand cmd = new NpgsqlCommand("select nextval('schema.foo_id_seq')", conn);
foreach (Parent p in parentList.Where(x => x.Id == null && x.Id == 0))
{
p.Id = Convert.ToInt64(cmd.ExecuteScalar());
p.Children.ForEach(x => x.ParentId = p.Id);
}
如果这些记录尚不存在,Where
子句可能并不重要...只是需要考虑一下。
从这里开始,您的 NpgsqlCopyIn
应该会在 parents 和 children 中表现出色。