批量插入之SqlBulkCopy

批量插入之SqlBulkCopy

作者:NewcatsHuang
时间:2021-12-25
完整代码:Github传送门

一.目录

二.方法介绍

1.for循环插入

对集合数据进行遍历,每次只插入集合的一条数据,对应的SQL语句为:

insert into UserInfo(Id,Name) values (@Id,@Name);

2.拼接sql

也需要for循环遍历,只是一条语句能插入多个数据,对应的SQL语句为:

insert into UserInfo(Id,Name) values (@Id1,@Name1),(@Id2,@Name2)...

3.SqlBulkCopy批量插入

利用各个数据库的特性,直接从文件复制到表

三.SqlBulkCopy介绍

1.SqlServer数据库

  • 使用 Microsoft.Data.SqlClient.SqlBulkCopy 类封装的方法

  • BULK INSERT Northwind.dbo.[OrderDetails] FROM 'f:\mydata\data.tbl' WITH (FORMATFILE='f:\mydata\data.fmt');

Microsoft SQL Server 包含一个名为 bcp 的受欢迎的命令行实用工具,以便将较大文件快速大容量复制到 SQL Server 数据库的表或视图中 。 SqlBulkCopy 类允许你编写可提供类似功能的托管代码解决方案。 还可通过其他方法将数据加载到 SQL Server 表(例如 INSERT 语句),但 SqlBulkCopy 可提供显著的性能优势。SqlBulkCopy 类可用于只将数据写入 SQLServer 表。但是, 数据源不限于 SQL Server;可以使用任何数据源,只要数据可以加载到 DataTable 实例或使用IDataReader 实例读取即可。
使用 SqlBulkCopy 类,你可以执行以下操作:
单次大容量复制操作
多次大容量复制操作
事务中的大容量复制操作

以上内容来自微软官方文档: Bulk Copy Operations in SQL Server

2.MySql数据库

  • 使用 MySqlConnector.MySqlBulkCopy 类封装的方法

  • LOAD DATA LOCAL INFILE '/tmp/test.txt' INTO TABLE test;

LOAD DATA语句以非常高的速度将文本文件中的行读取到表中。可以从服务器主机或客户端主机读取文件,具体取决于是否给出了修饰符。还会影响数据解释和错误处理。

以上内容来自MySql官网:LOAD DATA Statement

3.PostgreSql数据库

  • COPY public.userinfo (id,name,createtime) FROM STDIN (FORMAT BINARY);

COPY moves data between PostgreSQL tables and standard file-system files. COPY TO copies the contents of a table to a file, while COPY FROM copies data from a file to a table (appending the data to whatever is in the table already). COPY TO can also copy the results of a SELECT query.
If a column list is specified, COPY TO copies only the data in the specified columns to the file. For COPY FROM, each field in the file is inserted, in order, into the specified column. Table columns not specified in the COPY FROM column list will receive their default values.
COPY with a file name instructs the PostgreSQL server to directly read from or write to a file. The file must be accessible by the PostgreSQL user (the user ID the server runs as) and the name must be specified from the viewpoint of the server. When PROGRAM is specified, the server executes the given command and reads from the standard output of the program, or writes to the standard input of the program. The command must be specified from the viewpoint of the server, and be executable by the PostgreSQL user. When STDIN or STDOUT is specified, data is transmitted via the connection between the client and the server.
Each backend running COPY will report its progress in the pg_stat_progress_copy view. See Section 28.4.6 for details.

COPY在 PostgreSQL表和标准文件系统文件之间移动数据。 将表的内容复制到文件,同时将数据从文件复制到表(将数据追加到表中已有的任何内容)。 还可以复制查询的结果。COPY TOCOPY FROMCOPY TOSELECT
如果指定了列列表,则 仅将指定列中的数据复制到文件中。对于 ,文件中的每个字段将按顺序插入到指定的列中。列列表中未指定的表列将收到其默认值。COPY TOCOPY FROMCOPY FROM
COPY与文件名指示PostgreSQL服务器直接读取或写入文件。该文件必须可由PostgreSQL用户(服务器运行时的用户 ID)访问,并且必须从服务器的角度指定名称。当指定时,服务器执行给定的命令并从程序的标准输出中读取,或写入程序的标准输入。该命令必须从服务器的角度指定,并且可由PostgreSQL用户执行。指定 或 时,数据通过客户端和服务器之间的连接传输。PROGRAMSTDINSTDOUT
每个正在运行的后端都将在视图中报告其进度。有关详细信息,请参见第 28.4.6 节。COPYpg_stat_progress_copy

以上内容来自PostgreSql官网:sql copy

四.性能测试

1.环境

数据库 版本 OS CPU RAM 说明
SqlServer v2017 Win10 21H2 i7-9700k 64GB VMWare宿主机
MySql v8.0.27 Ubuntu Server 21.10 i7-9700k 2core 4GB VMWare虚拟机1
PostgreSql v13.5 Ubuntu Server 21.10 i7-9700k 2core 4GB VMWare虚拟机2

2.代码

2.1 for循环-dapper执行foreach循环插入数据

SqlServer版本

/// <summary>
/// dapper执行foreach循环插入数据
/// </summary>
internal long InsertForEach(List<NewcatsUserInfoTest> list)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    int result = 0;
    using (SqlConnection conn = new SqlConnection(ConnectionString))
    {
        foreach (NewcatsUserInfoTest test in list)
        {
            if (conn.State == ConnectionState.Closed)
                conn.Open();
            result += conn.Execute(SqlText, test, commandType: System.Data.CommandType.Text);
        }
    }
    sw.Stop();
    return sw.ElapsedMilliseconds;
}

2.2 for循环-ADO.NET的foreach循环插入数据

MySql版本

/// <summary>
/// ADO.NET的foreach循环插入数据
/// </summary>
internal long InsertForEachNative(List<NewcatsUserInfoTest> list)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    int result = 0;
    using (MySqlConnection conn = new MySqlConnection(ConnectionString))
    {
        if (conn.State == ConnectionState.Closed)
            conn.Open();
        foreach (NewcatsUserInfoTest item in list)
        {
            using (MySqlCommand cmd = new MySqlCommand())
            {
                string sqlText = $"INSERT INTO {TableName} (Id,Name,CreateTime) VALUES (@Id{result},@Name{result},@CreateTime{result})";
                cmd.Connection = conn;
                cmd.CommandText = sqlText;
                cmd.Parameters.Add(new MySqlParameter("@Id" + result.ToString(), item.Id));
                cmd.Parameters.Add(new MySqlParameter("@Name" + result.ToString(), item.Name));
                cmd.Parameters.Add(new MySqlParameter("@CreateTime" + result.ToString(), item.CreateTime));
                result += cmd.ExecuteNonQuery();
            }
        }
    }
    sw.Stop();
    return sw.ElapsedMilliseconds;
}

2.3 for循环-dapper直接传list参数

PostgreSql版本

/// <summary>
/// dapper直接传list参数
/// </summary>
internal long InsertBulk(List<NewcatsUserInfoTest> list)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    int result = 0;
    using (NpgsqlConnection conn = new NpgsqlConnection(ConnectionString))
    {
        if (conn.State == ConnectionState.Closed)
            conn.Open();
        result = conn.Execute(SqlText, list, commandType: System.Data.CommandType.Text);
    }
    sw.Stop();
    return sw.ElapsedMilliseconds;
}

2.4 for循环-dapper拼接sql语句

SqlServer版本

/// <summary>
/// dapper拼接sql语句
/// </summary>
internal long InsertAppend(List<NewcatsUserInfoTest> list)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    int result = 0;
    const int perCount = 500;
    int times = Convert.ToInt32(Math.Ceiling(list.Count * 1.0 / perCount));
    for (int i = 0; i < times; i++)
    {
        using (SqlConnection conn = new SqlConnection(ConnectionString))
        {
            if (conn.State == ConnectionState.Closed)
                conn.Open();
            StringBuilder sb = new StringBuilder($"INSERT INTO {TableName} (Id,Name,CreateTime) VALUES");
            var perList = list.Skip(i * perCount).Take(perCount);
            int index = 0;
            DynamicParameters dp = new DynamicParameters();
            foreach (NewcatsUserInfoTest test in perList)
            {
                sb.Append($"(@Id{index},@Name{index},@CreateTime{index}),");
                dp.Add($"@Id{index}", test.Id);
                dp.Add($"@Name{index}", test.Name);
                dp.Add($"@CreateTime{index}", test.CreateTime);
                index++;
            }
            result += conn.Execute(sb.ToString().TrimEnd(','), dp, commandType: System.Data.CommandType.Text);
        }
    }
    sw.Stop();
    return sw.ElapsedMilliseconds;
}

2.5 for循环-SqlBulkCopy插入-FromList

MySql版本

/// <summary>
/// SqlBulkCopy插入-FromList
/// </summary>
internal long SqlBulkCopyFromList(List<NewcatsUserInfoTest> list)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    int result = 0;
    using (MySqlConnection conn = new MySqlConnection(ConnectionString))
    {
        if (conn.State == ConnectionState.Closed)
            conn.Open();
        MySqlBulkCopy copy = new MySqlBulkCopy(conn);
        copy.DestinationTableName = TableName;
        var r = copy.WriteToServer(list.ToDataTable());
        result = r.RowsInserted;
    }
    sw.Stop();
    return sw.ElapsedMilliseconds;
}

2.6 for循环-SqlBulkCopy插入-FromDataTable

PostgreSql版本

/// <summary>
/// SqlBulkCopy插入-FromDataTable
/// </summary>
internal long SqlBulkCopyFromDataTable(DataTable dt)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    int result = 0;
    using (NpgsqlConnection conn = new NpgsqlConnection(ConnectionString))
    {
        if (conn.State == ConnectionState.Closed)
            conn.Open();
        using (NpgSqlBulkCopy copy = new NpgSqlBulkCopy(conn, TableName))
        {
            copy.WriteToServer(dt);
        }
    }
    sw.Stop();
    return sw.ElapsedMilliseconds;
}

2.7 Benchmark测试(SqlServer示例)

/// <summary>
/// SqlBulkCopy测试
/// </summary>
public class BulkCopyContext
{
    const int totalCount = 5000;

    #region SqlServer
    [Benchmark]
    public void SqlServer_InsertForEach()
    {
        List<NewcatsUserInfoTest> list = new List<NewcatsUserInfoTest>();
        for (int i = 0; i < totalCount; i++)
        {
            NewcatsUserInfoTest u = new NewcatsUserInfoTest()
            {
                Id = IdHelper.Create(),
                Name = EncryptHelper.GetRandomString(Random.Shared.Next(20)),
                CreateTime = DateTime.Now
            };
            list.Add(u);
        }
        SqlServerTest test = new SqlServerTest();
        test.Init();
        test.InsertForEach(list);
    }

    [Benchmark]
    public void SqlServer_InsertAppend()
    {
        List<NewcatsUserInfoTest> list = new List<NewcatsUserInfoTest>();
        for (int i = 0; i < totalCount; i++)
        {
            NewcatsUserInfoTest u = new NewcatsUserInfoTest()
            {
                Id = IdHelper.Create(),
                Name = EncryptHelper.GetRandomString(Random.Shared.Next(20)),
                CreateTime = DateTime.Now
            };
            list.Add(u);
        }
        SqlServerTest test = new SqlServerTest();
        test.Init();
        test.InsertAppend(list);
    }

    [Benchmark]
    public void SqlServer_SqlBulkCopy_FromList()
    {
        List<NewcatsUserInfoTest> list = new List<NewcatsUserInfoTest>();
        for (int i = 0; i < totalCount; i++)
        {
            NewcatsUserInfoTest u = new NewcatsUserInfoTest()
            {
                Id = IdHelper.Create(),
                Name = EncryptHelper.GetRandomString(Random.Shared.Next(20)),
                CreateTime = DateTime.Now
            };
            list.Add(u);
        }
        SqlServerTest test = new SqlServerTest();
        test.Init();
        test.SqlBulkCopyFromList(list);
    }

    [Benchmark]
    public void SqlServer_SqlBulkCopy_FromDataTable()
    {
        DataTable dt = new DataTable("NewcatsUserInfoTest");
        dt.Columns.Add("Id", typeof(long));
        dt.Columns.Add("Name", typeof(string));
        dt.Columns.Add("CreateTime", typeof(DateTime));
        for (int i = 0; i < totalCount; i++)
        {
            var id = IdHelper.Create();
            var name = EncryptHelper.GetRandomString(Random.Shared.Next(20));
            var now = DateTime.Now;
            dt.Rows.Add(id, name, now);
        }
        SqlServerTest test = new SqlServerTest();
        test.Init();
        test.SqlBulkCopyFromDataTable(dt);
    }
    #endregion
}

3.for循环测试结果

Database/Method Counts InsertForEach(ms) InsertForEachNative(ms) InsertBulk(ms) InsertAppend(ms) SqlBulkCopyFromList(ms) SqlBulkCopyFromDataTable(ms)
SqlServer 1 35 1 6 27 2
MySql 1 26 1 1 1 19 2
PostgreSql 1 10 2 3 42 1
SqlServer 10 42 5 1 6 25 1
MySql 10 35 6 6 1 20 2
PostgreSql 10 14 6 3 3 42 1
SqlServer 100 55 48 13 13 27 1
MySql 100 88 54 53 3 20 1
PostgreSql 100 37 52 68 4 43 2
SqlServer 1000 185 475 122 256 29 3
MySql 1000 630 582 560 19 25 7
PostgreSql 1000 549 474 521 14 45 4
SqlServer(1w) 10000 1330 4506 1216 2041 50 16
MySql 10000 6033 6815 6461 113 75 45
PostgreSql 10000 5310 5254 5131 80 72 26
SqlServer(10w) 100000 12500 43793 12065 19543 264 119
MySql 100000 61492 60210 61099 1201 534 394
PostgreSql 100000 53870 53407 55238 749 345 208
SqlServer(100w) 1000000 128015 452346 122330 194935 2253 1123
MySql 1000000 4766 3873
PostgreSql 1000000 2657 1351
SqlServer(1000w) 10000000 25721 15312
MySql 10000000 115881 49502 31687
PostgreSql 10000000 71680 25139 13041
展开查看详细的for循环测试结果

---
SqlServer测试结果如下:
集合大小:1

1.InsertForEach方法耗时:35ms

2.InsertForEachNative方法耗时:1ms

3.InsertBulk方法耗时:0ms

4.InsertAppend方法耗时:6ms

5.SqlBulkCopyFromList方法耗时:27ms

6.SqlBulkCopyFromDataTable方法耗时:2ms

MySql测试结果如下:
集合大小:1

1.InsertForEach方法耗时:26ms

2.InsertForEachNative方法耗时:1ms

3.InsertBulk方法耗时:1ms

4.InsertAppend方法耗时:1ms

5.SqlBulkCopyFromList方法耗时:19ms

6.SqlBulkCopyFromDataTable方法耗时:2ms

PostgreSql测试结果如下:
集合大小:1

1.InsertForEach方法耗时:10ms

2.InsertForEachNative方法耗时:2ms

3.InsertBulk方法耗时:0ms

4.InsertAppend方法耗时:3ms

5.SqlBulkCopyFromList方法耗时:42ms

6.SqlBulkCopyFromDataTable方法耗时:1ms

---

SqlServer测试结果如下:
集合大小:10

1.InsertForEach方法耗时:42ms

2.InsertForEachNative方法耗时:5ms

3.InsertBulk方法耗时:1ms

4.InsertAppend方法耗时:6ms

5.SqlBulkCopyFromList方法耗时:25ms

6.SqlBulkCopyFromDataTable方法耗时:1ms

MySql测试结果如下:
集合大小:10

1.InsertForEach方法耗时:35ms

2.InsertForEachNative方法耗时:6ms

3.InsertBulk方法耗时:6ms

4.InsertAppend方法耗时:1ms

5.SqlBulkCopyFromList方法耗时:20ms

6.SqlBulkCopyFromDataTable方法耗时:2ms

PostgreSql测试结果如下:
集合大小:10

1.InsertForEach方法耗时:14ms

2.InsertForEachNative方法耗时:6ms

3.InsertBulk方法耗时:3ms

4.InsertAppend方法耗时:3ms

5.SqlBulkCopyFromList方法耗时:42ms

6.SqlBulkCopyFromDataTable方法耗时:1ms

---

SqlServer测试结果如下:
集合大小:100

1.InsertForEach方法耗时:55ms

2.InsertForEachNative方法耗时:48ms

3.InsertBulk方法耗时:13ms

4.InsertAppend方法耗时:13ms

5.SqlBulkCopyFromList方法耗时:27ms

6.SqlBulkCopyFromDataTable方法耗时:1ms

MySql测试结果如下:
集合大小:100

1.InsertForEach方法耗时:88ms

2.InsertForEachNative方法耗时:54ms

3.InsertBulk方法耗时:53ms

4.InsertAppend方法耗时:3ms

5.SqlBulkCopyFromList方法耗时:20ms

6.SqlBulkCopyFromDataTable方法耗时:1ms

PostgreSql测试结果如下:
集合大小:100

1.InsertForEach方法耗时:37ms

2.InsertForEachNative方法耗时:52ms

3.InsertBulk方法耗时:68ms

4.InsertAppend方法耗时:4ms

5.SqlBulkCopyFromList方法耗时:43ms

6.SqlBulkCopyFromDataTable方法耗时:2ms

---

SqlServer测试结果如下:
集合大小:1000

1.InsertForEach方法耗时:185ms

2.InsertForEachNative方法耗时:475ms

3.InsertBulk方法耗时:122ms

4.InsertAppend方法耗时:256ms

5.SqlBulkCopyFromList方法耗时:29ms

6.SqlBulkCopyFromDataTable方法耗时:3ms

MySql测试结果如下:
集合大小:1000

1.InsertForEach方法耗时:630ms

2.InsertForEachNative方法耗时:582ms

3.InsertBulk方法耗时:560ms

4.InsertAppend方法耗时:19ms

5.SqlBulkCopyFromList方法耗时:25ms

6.SqlBulkCopyFromDataTable方法耗时:7ms

PostgreSql测试结果如下:
集合大小:1000

1.InsertForEach方法耗时:549ms

2.InsertForEachNative方法耗时:474ms

3.InsertBulk方法耗时:521ms

4.InsertAppend方法耗时:14ms

5.SqlBulkCopyFromList方法耗时:45ms

6.SqlBulkCopyFromDataTable方法耗时:4ms

---

SqlServer测试结果如下:
集合大小:10000

1.InsertForEach方法耗时:1330ms

2.InsertForEachNative方法耗时:4506ms

3.InsertBulk方法耗时:1216ms

4.InsertAppend方法耗时:2041ms

5.SqlBulkCopyFromList方法耗时:50ms

6.SqlBulkCopyFromDataTable方法耗时:16ms

MySql测试结果如下:
集合大小:10000

1.InsertForEach方法耗时:6033ms

2.InsertForEachNative方法耗时:6815ms

3.InsertBulk方法耗时:6461ms

4.InsertAppend方法耗时:113ms

5.SqlBulkCopyFromList方法耗时:75ms

6.SqlBulkCopyFromDataTable方法耗时:45ms

PostgreSql测试结果如下:
集合大小:10000

1.InsertForEach方法耗时:5310ms

2.InsertForEachNative方法耗时:5254ms

3.InsertBulk方法耗时:5131ms

4.InsertAppend方法耗时:80ms

5.SqlBulkCopyFromList方法耗时:72ms

6.SqlBulkCopyFromDataTable方法耗时:26ms

---

SqlServer测试结果如下:
集合大小:100000

1.InsertForEach方法耗时:12500ms

2.InsertForEachNative方法耗时:43793ms

3.InsertBulk方法耗时:12065ms

4.InsertAppend方法耗时:19543ms

5.SqlBulkCopyFromList方法耗时:264ms

6.SqlBulkCopyFromDataTable方法耗时:119ms

MySql测试结果如下:
集合大小:100000

1.InsertForEach方法耗时:61492ms

2.InsertForEachNative方法耗时:60210ms

3.InsertBulk方法耗时:61099ms

4.InsertAppend方法耗时:1201ms

5.SqlBulkCopyFromList方法耗时:534ms

6.SqlBulkCopyFromDataTable方法耗时:394ms

PostgreSql测试结果如下:
集合大小:100000

1.InsertForEach方法耗时:53870ms

2.InsertForEachNative方法耗时:53407ms

3.InsertBulk方法耗时:55238ms

4.InsertAppend方法耗时:749ms

5.SqlBulkCopyFromList方法耗时:345ms

6.SqlBulkCopyFromDataTable方法耗时:208ms

---

SqlServer测试结果如下:
集合大小:1000000

1.InsertForEach方法耗时:128015ms

2.InsertForEachNative方法耗时:452346ms

3.InsertBulk方法耗时:122330ms

4.InsertAppend方法耗时:194935ms

5.SqlBulkCopyFromList方法耗时:2253ms

6.SqlBulkCopyFromDataTable方法耗时:1123ms

MySql测试结果如下:
集合大小:1000000

1.InsertForEach方法耗时:0ms

2.InsertForEachNative方法耗时:0ms

3.InsertBulk方法耗时:0ms

4.InsertAppend方法耗时:11982ms

5.SqlBulkCopyFromList方法耗时:4766ms

6.SqlBulkCopyFromDataTable方法耗时:3873ms

PostgreSql测试结果如下:
集合大小:1000000

1.InsertForEach方法耗时:0ms

2.InsertForEachNative方法耗时:0ms

3.InsertBulk方法耗时:0ms

4.InsertAppend方法耗时:7101ms

5.SqlBulkCopyFromList方法耗时:2657ms

6.SqlBulkCopyFromDataTable方法耗时:1351ms

---

SqlServer测试结果如下:
集合大小:10000000

1.InsertForEach方法耗时:0ms

2.InsertForEachNative方法耗时:0ms

3.InsertBulk方法耗时:0ms

4.InsertAppend方法耗时:0ms

5.SqlBulkCopyFromList方法耗时:25721ms

6.SqlBulkCopyFromDataTable方法耗时:15312ms

MySql测试结果如下:
集合大小:10000000

1.InsertForEach方法耗时:0ms

2.InsertForEachNative方法耗时:0ms

3.InsertBulk方法耗时:0ms

4.InsertAppend方法耗时:115881ms

5.SqlBulkCopyFromList方法耗时:49502ms

6.SqlBulkCopyFromDataTable方法耗时:31687ms

PostgreSql测试结果如下:
集合大小:10000000

1.InsertForEach方法耗时:0ms

2.InsertForEachNative方法耗时:0ms

3.InsertBulk方法耗时:0ms

4.InsertAppend方法耗时:71680ms

5.SqlBulkCopyFromList方法耗时:25139ms

6.SqlBulkCopyFromDataTable方法耗时:13041ms

---


4.Benchmark测试结果

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19044.1415 (21H2)
Intel Core i7-9700K CPU 3.60GHz (Coffee Lake), 1 CPU, 8 logical and 8 physical cores
.NET SDK=6.0.101
  [Host]     : .NET 6.0.1 (6.0.121.56705), X64 RyuJIT
  DefaultJob : .NET 6.0.1 (6.0.121.56705), X64 RyuJIT

totalCount=1

Method Mean Error StdDev
SqlServer_InsertForEach 1.451 ms 0.0046 ms 0.0043 ms
SqlServer_InsertAppend 1.450 ms 0.0074 ms 0.0066 ms
SqlServer_SqlBulkCopy_FromList 2.060 ms 0.0322 ms 0.0285 ms
SqlServer_SqlBulkCopy_FromDataTable 2.054 ms 0.0407 ms 0.0381 ms
MySql_InsertForEach 7.352 ms 0.1453 ms 0.3033 ms
MySql_InsertAppend 7.553 ms 0.1501 ms 0.2338 ms
MySql_SqlBulkCopy_FromList 7.875 ms 0.1539 ms 0.2735 ms
MySql_SqlBulkCopy_FromDataTable 7.969 ms 0.1549 ms 0.1722 ms
PostgreSql_InsertForEach 2.091 ms 0.0416 ms 0.1082 ms
PostgreSql_InsertAppend 2.095 ms 0.0416 ms 0.1005 ms
PostgreSql_SqlBulkCopy_FromList 4.061 ms 0.0804 ms 0.1449 ms
PostgreSql_SqlBulkCopy_FromDataTable 4.097 ms 0.0808 ms 0.1436 ms

totalCount=10

Method Mean Error StdDev
SqlServer_InsertForEach 2.476 ms 0.0091 ms 0.0085 ms
SqlServer_InsertAppend 1.776 ms 0.0129 ms 0.0121 ms
SqlServer_SqlBulkCopy_FromList 2.079 ms 0.0175 ms 0.0146 ms
SqlServer_SqlBulkCopy_FromDataTable 2.081 ms 0.0264 ms 0.0220 ms
MySql_InsertForEach 13.359 ms 0.2609 ms 0.4212 ms
MySql_InsertAppend 7.787 ms 0.1556 ms 0.3350 ms
MySql_SqlBulkCopy_FromList 8.336 ms 0.1659 ms 0.3157 ms
MySql_SqlBulkCopy_FromDataTable 8.437 ms 0.1682 ms 0.2946 ms
PostgreSql_InsertForEach 7.144 ms 0.1386 ms 0.1898 ms
PostgreSql_InsertAppend 2.115 ms 0.0418 ms 0.0775 ms
PostgreSql_SqlBulkCopy_FromList 4.020 ms 0.0799 ms 0.1684 ms
PostgreSql_SqlBulkCopy_FromDataTable 4.082 ms 0.0772 ms 0.1373 ms

totalCount=100

Method Mean Error StdDev
SqlServer_InsertForEach 13.920 ms 0.1864 ms 0.1652 ms
SqlServer_InsertAppend 8.570 ms 0.0443 ms 0.0393 ms
SqlServer_SqlBulkCopy_FromList 2.432 ms 0.0477 ms 0.0567 ms
SqlServer_SqlBulkCopy_FromDataTable 2.339 ms 0.0361 ms 0.0338 ms
MySql_InsertForEach 63.129 ms 1.2535 ms 2.6983 ms
MySql_InsertAppend 8.332 ms 0.1603 ms 0.1646 ms
MySql_SqlBulkCopy_FromList 8.549 ms 0.1457 ms 0.2589 ms
MySql_SqlBulkCopy_FromDataTable 8.567 ms 0.1697 ms 0.2434 ms
PostgreSql_InsertForEach 55.740 ms 1.1029 ms 2.3503 ms
PostgreSql_InsertAppend 2.853 ms 0.0550 ms 0.0752 ms
PostgreSql_SqlBulkCopy_FromList 4.393 ms 0.0870 ms 0.1591 ms
PostgreSql_SqlBulkCopy_FromDataTable 4.373 ms 0.0872 ms 0.1528 ms

totalCount=1000

Method Mean Error StdDev
SqlServer_InsertForEach 127.432 ms 0.6471 ms 0.5737 ms
SqlServer_InsertAppend 230.073 ms 0.4374 ms 0.3877 ms
SqlServer_SqlBulkCopy_FromList 5.425 ms 0.0194 ms 0.0162 ms
SqlServer_SqlBulkCopy_FromDataTable 5.283 ms 0.0263 ms 0.0233 ms
MySql_InsertForEach 606.386 ms 11.0222 ms 16.8320 ms
MySql_InsertAppend 20.716 ms 0.3511 ms 0.3285 ms
MySql_SqlBulkCopy_FromList 15.641 ms 0.3126 ms 0.4065 ms
MySql_SqlBulkCopy_FromDataTable 15.161 ms 0.2908 ms 0.3461 ms
PostgreSql_InsertForEach 540.577 ms 10.7744 ms 23.4226 ms
PostgreSql_InsertAppend 8.047 ms 0.1173 ms 0.1097 ms
PostgreSql_SqlBulkCopy_FromList 7.259 ms 0.1192 ms 0.1057 ms
PostgreSql_SqlBulkCopy_FromDataTable 7.232 ms 0.1189 ms 0.0993 ms

totalCount=10000

Method Mean Error StdDev
SqlServer_InsertForEach 1,253.01 ms 2.507 ms 2.345 ms
SqlServer_InsertAppend 1,965.34 ms 2.126 ms 1.884 ms
SqlServer_SqlBulkCopy_FromList 30.06 ms 0.228 ms 0.202 ms
SqlServer_SqlBulkCopy_FromDataTable 27.53 ms 0.332 ms 0.310 ms
MySql_InsertForEach 5,967.03 ms 116.450 ms 114.369 ms
MySql_InsertAppend 149.48 ms 2.252 ms 1.758 ms
MySql_SqlBulkCopy_FromList 84.15 ms 1.662 ms 3.358 ms
MySql_SqlBulkCopy_FromDataTable 82.42 ms 1.648 ms 3.476 ms
PostgreSql_InsertForEach 5,357.35 ms 106.063 ms 155.466 ms
PostgreSql_InsertAppend 72.69 ms 0.354 ms 0.296 ms
PostgreSql_SqlBulkCopy_FromList 31.36 ms 0.623 ms 0.718 ms
PostgreSql_SqlBulkCopy_FromDataTable 31.37 ms 0.619 ms 0.737 ms

totalCount=100000

Method Mean Error StdDev
SqlServer_InsertForEach 12,633.6 ms 216.57 ms 202.58 ms
SqlServer_InsertAppend 19,484.5 ms 35.34 ms 29.51 ms
SqlServer_SqlBulkCopy_FromList 263.1 ms 5.13 ms 6.67 ms
SqlServer_SqlBulkCopy_FromDataTable 239.3 ms 4.53 ms 5.56 ms
MySql_InsertForEach 59,331.8 ms 1,135.02 ms 1,261.57 ms
MySql_InsertAppend 1,227.6 ms 23.60 ms 25.25 ms
MySql_SqlBulkCopy_FromList 540.4 ms 10.71 ms 21.64 ms
MySql_SqlBulkCopy_FromDataTable 515.3 ms 10.16 ms 20.28 ms
PostgreSql_InsertForEach 53,797.8 ms 740.99 ms 693.12 ms
PostgreSql_InsertAppend 722.3 ms 8.35 ms 7.40 ms
PostgreSql_SqlBulkCopy_FromList 310.4 ms 3.55 ms 3.14 ms
PostgreSql_SqlBulkCopy_FromDataTable 280.6 ms 5.55 ms 9.57 ms

5.结论

  • 注:10W条数据的典型场景
项目 SqlServer MySql PostgreSql
for循环 for循环 for循环 for循环
最快 119ms(0.12s) 394ms(0.39s) 208ms(0.2s)
最慢 43793ms(43.79s) 61492ms(61.49s) 55238ms(55.23s)
差距 368倍 156倍 265倍
- - - -
Benchmark Benchmark Benchmark Benchmark
最快 239ms(0.24s) 515ms(0.51s) 280ms(0.28s)
最慢 19484ms(19.48s) 59331ms(59.33s) 53797ms(53.79s)
差距 81倍 115倍 192倍
  • SqlBulkCopy方法能显著提高批量插入性能
  • SqlServer的insert () values(),(),()....语句似乎没有优化
  • SqlServer的整体表现比MySql和PostgreSql好一些(都是空数据库,表结构简单,应该还没有达到硬件限制)
  • MySql和PostgreSql的insert () values(),(),()....语句性能不错,尤其是PostgreSql
  • PostgreSql各项指标均优于MySql

五.注意事项

  • 构建的DataTable要跟数据库表完全一致,包含自增列,排除NotMapped
  • 构建DataColumn时列名要跟表一致,类型要传实际类型,不能不传或者传object
  • MySql连接字符串需要加上AllowLoadLocalInfile=true且服务端设置local_infile=1(建议修改全局配置文件)
  • PostgreSql的copy指令对表名大小写有特殊要求,建议建表和实体特性都使用小写
  • PostgreSql的NpgsqlBinaryImporter.Write(,)类型要求和数据库一致,需要使用枚举NpgsqlDbType

作者:NewcatsHuang
时间:2021-12-25
完整代码:https://github.com/newcatshuang/Newcats.Infrastructure/tree/master/tests/SqlBulkCopyTest
转载请注明出处,谢谢O(∩_∩)O

posted @ 2022-01-16 12:19  Newcats  阅读(1196)  评论(0编辑  收藏  举报