Which RDBMS has the fastest .NET Bulk Insert implementation?
The Bulk Insert support in the latest release of OrmLite enables accessing
different efficient ways made available for each RDBMS for
inserting large amounts of data from a .NET App, encapsulated behind OrmLite's new BulkInsert
API:
db.BulkInsert(rows);
Bulk Insert Implementations​
Where the optimal implementation for each RDBMS utilize the different available RDBMS-specific implementations below:
- PostgreSQL - Uses PostgreSQL's COPY command via Npgsql's Binary Copy import
- MySql - Uses MySqlBulkLoader
feature where data is written to a temporary CSV file that's imported directly by
MySqlBulkLoader
- MySqlConnector - Uses MySqlConnector's MySqlBulkLoader
implementation which makes use of its
SourceStream
feature to avoid writing to a temporary file - SQL Server - Uses SQL Server's
SqlBulkCopy
feature which imports data written to an in-memoryDataTable
- SQLite - SQLite doesn't have a specific import feature, instead Bulk Inserts are performed using batches of Multiple Rows Inserts to reduce I/O calls down to a configurable batch size
- Firebird - Is also implemented using Multiple Rows Inserts within an EXECUTE BLOCK configurable up to Firebird's maximum of 256 statements
SQL Multiple Row Inserts​
An efficient and compact alternative to inserting large amounts of data that enjoys broad support from all RDBMS's is to use SQL's Multiple Insert Rows feature to insert multiple rows within a single INSERT statement:
INSERT INTO Contact (Id, FirstName, LastName, Age) VALUES
(1, 'John', 'Doe', 21),
(2, 'Jane', 'Doe', 27),
(3, 'Jack', 'Doe', 42);
Normally OrmLite APIs uses parameterized statements however for SQL Multiple Row Inserts it uses inline rasterized values in order to construct and send large SQL INSERT statements that avoids RDBMS's max parameter limitations, which if preferred can be configured to be used instead of its default optimal Bulk Insert implementation:
db.BulkInsert(rows, new BulkInsertConfig {
Mode = BulkInsertMode.Sql
});
Batch Size​
Multiple Row Inserts are sent in batches of 1000 (Maximum for SQL Server), Firebird uses a maximum of 256 whilst other RDBMS's can be configured to use larger batch sizes:
db.BulkInsert(rows, new BulkInsertConfig {
BatchSize = 1000
});
Benchmarks​
All benchmarks were run with BenchmarkDotNet running on an macOS M2 Apple Macbook, an Ubuntu 22.04 Linux VM and an Windows 10 Intel iMac 5K.
Hopefully these benchmarks are informative in showing the performance you can expect from Bulk Inserts for each popular RDBMS's across on different operating systems. For context, these benchmarks were run on the configurations below:
Apple M2 / 24GB RAM​
- macOS / ARM
- .NET 6.0 / ARM
- RDBMS run from local Docker containers
Linux VM / 7GB RAM​
- Ubuntu 22.04 LTS / x64
- .NET 6.0 / x64
- RDBMS run from local Docker containers
Intel iMac 5K / 24GB RAM​
- Windows 10 / x64
- .NET 6.0 / x64
- RDBMS run from local Windows installations
Performance is also affected by the quality and efficiency of the ADO .NET library implementations specific to each RDBMS, which can result in non .NET languages yielding different results based on the quality of their data access implementations.
Apple M2 Benchmarks​
Apple M2 Benchmarks were run on an Apple M2 Macbook Air 15" / 24GB RAM, specifications reported by BenchmarkDotNet:
BenchmarkDotNet v0.13.6, macOS Ventura 13.4.1 (22F2083) [Darwin 22.5.0]
Apple M2, 1 CPU, 8 logical and 8 physical cores
.NET SDK 6.0.411
[Host] : .NET 6.0.19 (6.0.1923.31806), Arm64 RyuJIT AdvSIMD
Job-VQWEED : .NET 6.0.19 (6.0.1923.31806), Arm64 RyuJIT AdvSIMD
All benchmarks were run against local databases with most RDBMS's installed and running from a Docker container.
All Docker containers runs native ARM Builds whilst SQL Server's AMD64 Docker image runs emulated on Rosetta using Apple's Virtualization Framework.
Optimized Bulk Insert Performance​
These benchmarks below uses the default optimal Bulk Insert implementation for each RDBMS:
SQL Server results removed due to poor outlier performance
Inserting 1,000 Rows​
Database | Relative | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
SQLite Memory | 1x | 1.812 ms | 0.0355 ms | 0.0520 ms | 1.813 ms |
SQLite Disk | 1.29x | 2.330 ms | 0.0463 ms | 0.0664 ms | 2.331 ms |
PostgreSQL | 3.38x | 6.123 ms | 0.2952 ms | 0.8705 ms | 6.280 ms |
MySqlConnector | 4.84x | 8.778 ms | 0.2387 ms | 0.6962 ms | 8.821 ms |
MySql | 5.34x | 9.676 ms | 0.2860 ms | 0.8159 ms | 9.575 ms |
SqlServer | 7.27x | 13.182 ms | 0.3376 ms | 0.9795 ms | 12.987 ms |
Inserting 10,000 Rows​
Database | Relative | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
PostgreSQL | 1x | 14.389 ms | 0.4020 ms | 1.1533 ms | 14.181 ms |
SQLite Memory | 1.19x | 17.066 ms | 0.3119 ms | 0.4269 ms | 16.899 ms |
SQLite Disk | 1.41x | 20.224 ms | 0.3869 ms | 0.4139 ms | 20.212 ms |
MySql | 4.47x | 64.389 ms | 1.3736 ms | 3.9411 ms | 65.195 ms |
MySqlConnector | 4.48x | 64.427 ms | 1.4085 ms | 3.9496 ms | 64.854 ms |
SqlServer | 6.24x | 89.821 ms | 2.9396 ms | 8.6213 ms | 91.203 ms |
Inserting 100,000 Rows​
Database | Relative | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
PostgreSQL | 1x | 115.645 ms | 3.6250 ms | 10.5744 ms | 111.401 ms |
SQLite Memory | 1.44x | 166.747 ms | 0.4975 ms | 0.4154 ms | 166.742 ms |
SQLite Disk | 1.73x | 199.697 ms | 3.8056 ms | 5.9249 ms | 201.890 ms |
MySqlConnector | 2.67x | 308.574 ms | 6.0372 ms | 7.8501 ms | 307.322 ms |
MySql | 2.69x | 310.966 ms | 6.2043 ms | 10.3660 ms | 308.421 ms |
SqlServer | 7.22x | 835.181 ms | 16.5847 ms | 15.5133 ms | 836.200 ms |
Inserting 1,000,000 Rows​
Database | Relative | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
PostgreSQL | 1x | 980.558 ms | 18.6512 ms | 39.3418 ms | 973.302 ms |
SQLite Memory | 1.73x | 1,697.041 ms | 12.3617 ms | 11.5632 ms | 1,692.348 ms |
SQLite Disk | 2.01x | 1,975.092 ms | 15.7094 ms | 13.1181 ms | 1,971.127 ms |
MySqlConnector | 2.71x | 2,654.076 ms | 37.9302 ms | 33.6241 ms | 2,662.150 ms |
MySql | 2.71x | 2,661.294 ms | 29.3865 ms | 26.0504 ms | 2,661.826 ms |
SqlServer | 8.67x | 8,517.809 ms | 55.3398 ms | 49.0573 ms | 8,514.901 ms |
Effectively showing PostgreSQL binary COPY is the fastest Bulk Insert implementation of all RDBMS providers, we're also seeing the overhead of running SQL Server's AMD64 images on ARM having a significant impact on its performance.
Multiple Inserts Rows Performance​
These benchmarks show the performance of executing Multiple Row Inserts in batches of 1000 which is a good example to show the comparative performance of each RDBMS for executing large SQL Insert statements:
SQL Server results removed due to poor outlier performance
Inserting 1,000 Rows​
Database | Relative | Mean | Error | StdDev |
---|---|---|---|---|
SQLite Memory | 1x | 1.774 ms | 0.0331 ms | 0.0554 ms |
SQLite Disk | 1.30x | 2.306 ms | 0.0398 ms | 0.0531 ms |
PostgreSQL | 4.55x | 8.073 ms | 0.5297 ms | 1.5452 ms |
MySqlConnector | 5.16x | 9.154 ms | 0.3355 ms | 0.9681 ms |
MySql | 6.32x | 11.210 ms | 0.3573 ms | 1.0252 ms |
SqlServer | 49.11x | 87.128 ms | 0.8415 ms | 0.7460 ms |
Inserting 10,000 Rows​
Database | Relative | Mean | Error | StdDev |
---|---|---|---|---|
SQLite Memory | 1x | 16.724 ms | 0.3183 ms | 0.3406 ms |
SQLite Disk | 1.20x | 20.149 ms | 0.2649 ms | 0.2212 ms |
PostgreSQL | 2.90x | 48.502 ms | 2.3704 ms | 6.9893 ms |
MySqlConnector | 4.78x | 79.905 ms | 2.5416 ms | 7.4540 ms |
MySql | 5.97x | 98.179 ms | 2.5259 ms | 7.4080 ms |
SqlServer | 50.28x | 840.832 ms | 3.6211 ms | 2.8271 ms |
Inserting 100,000 Rows​
Database | Relative | Mean | Error | StdDev |
---|---|---|---|---|
SQLite Memory | 1x | 168.207 ms | 1.8704 ms | 1.4603 ms |
SQLite Disk | 1.20x | 201.074 ms | 3.7597 ms | 5.3920 ms |
PostgreSQL | 2.53x | 425.789 ms | 8.4799 ms | 12.6922 ms |
MySqlConnector | 3.75x | 630.839 ms | 11.4516 ms | 19.1330 ms |
MySql | 4.27x | 718.902 ms | 14.0201 ms | 15.5833 ms |
SqlServer | 50.35x | 8,469.901 ms | 48.8046 ms | 43.2640 ms |
Inserting 1,000,000 Rows​
Database | Relative | Mean | Error | StdDev |
---|---|---|---|---|
SQLite Memory | 1x | 1,710.710 ms | 5.0494 ms | 4.2165 ms |
SQLite Disk | 1.34x | 1,949.376 ms | 7.3221 ms | 6.4909 ms |
PostgreSQL | 2.93x | 5,009.395 ms | 99.4490 ms | 93.0246 ms |
MySqlConnector | 3.82x | 6,541.659 ms | 61.4450 ms | 57.4757 ms |
MySql | 4.04x | 6,905.332 ms | 72.1600 ms | 63.9680 ms |
SqlServer | 52.31x | 89,485.334 ms | 713.9731 ms | 596.1999 ms |
Takeaways from these results show PostgreSQL continuing to be a star performer, consistently out-performing other distributed RDBMS's whilst SQL Server yields even worse comparative performances.
Single Insert Performance​
This benchmark measures the performance of multiple single inserts, i.e. using the traditional Single Insert ORM APIs when Bulk Insert is not available:
Inserting 1,000 Rows​
Database | Relative | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
SQLite Memory | 1x | 15,417.3 μs | 1,820.82 μs | 5,311.41 μs | 18,153.2 μs |
SQLite Disk | 14.54x | 224,237.9 μs | 1,777.94 μs | 1,388.10 μs | 224,184.8 μs |
PostgreSQL | 22.65x | 349,232.8 μs | 3,255.67 μs | 2,541.81 μs | 349,404.4 μs |
SqlServer | 63.44x | 978,002.9 μs | 19,327.88 μs | 19,848.30 μs | 982,696.9 μs |
MySqlConnector | 78.45x | 1,209,468.9 μs | 24,150.34 μs | 38,305.00 μs | 1,201,763.9 μs |
MySql | 82.51x | 1,272,097.5 μs | 24,790.10 μs | 38,595.20 μs | 1,287,374.8 μs |
Where we can see that PostgreSQL/Npgsql holds the performance crown for Multiple Single Inserts, in addition to Multiple Rows Inserts and Bulk Inserts implementations.
We can the use this compare the relative performance benefits of Bulk Inserts vs Multiple Rows Inserts vs Single Inserts of each database for 1,000 records:
Database | Bulk Inserts | Multiple Rows Inserts | Single Row Inserts |
---|---|---|---|
SQLite Disk | 1x | 1x | 97.24x |
PostgreSQL | 1x | 1.32x | 57.04x |
MySqlConnector | 1x | 1.04x | 137.78x |
MySql | 1x | 1.16x | 131.47x |
SqlServer | 1x | 6.61x | 74.19x |
Effectively showing the cost of Single Inserts multiple I/O calls vs Bulk or SQL Batch Inserts - confirming that Bulk Inserts offer much better performance when needing to insert a significant number of rows.
Relative performance for Inserting 10,000 records:
Database | Bulk Inserts | Multiple Rows Inserts |
---|---|---|
SQLite Disk | 1x | 1x |
PostgreSQL | 1x | 3.37x |
MySqlConnector | 1x | 1.24x |
MySql | 1x | 1.52x |
SqlServer | 1x | 9.36x |
Relative performance for Inserting 100,000 records:
Database | Bulk Inserts | Multiple Rows Inserts |
---|---|---|
SQLite Disk | 1x | 1.01x |
PostgreSQL | 1x | 3.68x |
MySqlConnector | 1x | 2.04x |
MySql | 1x | 2.31x |
SqlServer | 1x | 10.14x |
Showing large batched SQL Insert statements staying within 2-3.7x performance range of their efficient Bulk Insert
implementations, except for SQL Server which is an order of magnitude slower than SqlBulkCopy
Ubuntu Linux VM Benchmarks​
The Ubuntu Linux VM Benchmarks were run on a GitHub Actions VM which are reported to run on a 2 Core CPU with 7 GB of RAM, specifications reported by BenchmarkDotnet:
BenchmarkDotNet v0.13.6, Ubuntu 22.04.2 LTS (Jammy Jellyfish)
Intel Xeon Platinum 8370C CPU 2.80GHz, 1 CPU, 2 logical and 2 physical cores
.NET SDK 6.0.412
[Host] : .NET 6.0.20 (6.0.2023.32017), X64 RyuJIT AVX2
Job-HXEVFT : .NET 6.0.20 (6.0.2023.32017), X64 RyuJIT AVX2
Optimized Bulk Insert Performance​
These benchmarks below uses the default optimal Bulk Insert implementation for each RDBMS:
Inserting 1,000 Rows​
Database | Relative | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
SQLite Memory | 1x | 3.025 ms | 0.0527 ms | 0.1089 ms | 3.053 ms |
SQLite Disk | 1.49x | 4.520 ms | 0.0898 ms | 0.1794 ms | 4.576 ms |
PostgreSQL | 1.87x | 5.646 ms | 0.2275 ms | 0.6415 ms | 5.630 ms |
MySqlConnector | 3.50x | 10.597 ms | 0.2045 ms | 0.1813 ms | 10.608 ms |
SqlServer | 4.03x | 12.201 ms | 0.2420 ms | 0.5845 ms | 12.104 ms |
MySql | 17.75x | 53.698 ms | 1.0640 ms | 2.0753 ms | 53.425 ms |
As the poor MySql performance is more prevalent the smaller number of rows are Bulk Inserted suggests it's the result of a 1-time cost like writing to the temporary file, which is less of a overhead when more rows are inserted.
Inserting 10,000 Rows​
Database | Relative | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
PostgreSQL | 1x | 26.436 ms | 1.8383 ms | 5.1848 ms | 23.624 ms |
SQLite Memory | 1.05x | 27.653 ms | 0.5439 ms | 1.0478 ms | 27.100 ms |
SQLite Disk | 1.52x | 40.234 ms | 0.7801 ms | 1.2817 ms | 40.189 ms |
MySqlConnector | 3.04x | 80.429 ms | 1.6022 ms | 2.0833 ms | 80.585 ms |
MySql | 3.05x | 80.535 ms | 1.5778 ms | 2.5478 ms | 80.400 ms |
SqlServer | 3.59x | 94.899 ms | 1.8399 ms | 2.6388 ms | 95.128 ms |
PostgreSQL continues to shine on its native Linux platform which somehow has a magically efficient implementation that bests bulk inserts to an In Memory SQLite database?
Inserting 100,000 Rows​
Database | Relative | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
PostgreSQL | 1x | 211.951 ms | 4.2186 ms | 11.1134 ms | 210.844 ms |
SQLite Memory | 1.43x | 303.348 ms | 5.9261 ms | 8.6864 ms | 305.627 ms |
SQLite Disk | 1.97x | 417.378 ms | 8.0392 ms | 8.6019 ms | 418.351 ms |
MySqlConnector | 3.17x | 670.859 ms | 11.1014 ms | 9.8411 ms | 668.926 ms |
MySql | 3.28x | 693.690 ms | 8.6160 ms | 8.0594 ms | 691.702 ms |
SqlServer | 4.66x | 986.676 ms | 19.0059 ms | 18.6663 ms | 983.779 ms |
Here we see that SQL Server AMD64 image is not just slow under Rosetta/ARM, it's just consistently slow on Linux - surprising how much slower a multi-billion dollar commercial database is vs the Free and Open Source PostgreSQL.
Multiple Inserts Rows Performance​
These benchmarks show the performance of executing Multiple Row Inserts in batches of 1000 rows:
SQL Server results removed due to poor outlier performance
Inserting 1,000 Rows​
Database | Relative | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
SQLite Memory | 1x | 3.117 ms | 0.0293 ms | 0.0274 ms | 3.116 ms |
SQLite Disk | 1.46x | 4.543 ms | 0.0898 ms | 0.2235 ms | 4.519 ms |
PostgreSQL | 2.40x | 7.481 ms | 0.1495 ms | 0.1325 ms | 7.469 ms |
MySqlConnector | 3.86x | 12.025 ms | 0.2265 ms | 0.2946 ms | 11.966 ms |
MySql | 4.88x | 15.204 ms | 0.3003 ms | 0.7254 ms | 15.106 ms |
SqlServer | 39.61x | 123.473 ms | 2.2482 ms | 1.9930 ms | 123.270 ms |
Inserting 10,000 Rows​
Database | Relative | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
SQLite Memory | 1x | 28.106 ms | 0.5567 ms | 1.1621 ms | 27.552 ms |
SQLite Disk | 1.42x | 39.774 ms | 0.5604 ms | 0.4968 ms | 39.701 ms |
PostgreSQL | 2.33x | 65.437 ms | 1.2455 ms | 1.4343 ms | 65.431 ms |
MySqlConnector | 3.57x | 100.473 ms | 1.9342 ms | 1.7146 ms | 100.253 ms |
MySql | 5.07x | 142.609 ms | 9.0717 ms | 26.3187 ms | 130.328 ms |
SqlServer | 41.47x | 1,165.550 ms | 11.0161 ms | 9.7655 ms | 1,167.982 ms |
Inserting 100,000 Rows​
Database | Relative | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
SQLite Memory | 1x | 302.656 ms | 5.9832 ms | 6.4019 ms | 301.417 ms |
SQLite Disk | 1.40x | 424.378 ms | 6.9570 ms | 6.5076 ms | 424.492 ms |
PostgreSQL | 2.15x | 649.366 ms | 10.3512 ms | 9.6826 ms | 651.269 ms |
MySqlConnector | 3.03x | 917.979 ms | 7.5867 ms | 6.7254 ms | 917.177 ms |
MySql | 4.24x | 1,282.074 ms | 25.3866 ms | 69.0662 ms | 1,277.800 ms |
SqlServer | 39.24x | 11,875.196 ms | 190.5093 ms | 178.2025 ms | 11,881.457 ms |
SQL Server's performance goes from slow to abysmal when executing large SQL Insert statements, effectively suggesting there's no good way to import large amounts of data in a SQL Server Linux instance from code, you'll likely get better performance from an external solution like bcp utility or SSIS.
Single Insert Performance​
This benchmark measures the performance of multiple single inserts:
Inserting 1,000 Rows​
Database | Relative | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
SQLite Memory | 1x | 15.970 ms | 0.3430 ms | 1.0004 ms | 15.334 ms |
PostgreSQL | 19.78x | 315.935 ms | 6.3063 ms | 16.6133 ms | 316.822 ms |
SqlServer | 43.23x | 690.398 ms | 13.6920 ms | 36.3094 ms | 685.644 ms |
MySqlConnector | 46.79x | 747.250 ms | 14.5164 ms | 19.3789 ms | 748.131 ms |
SQLite Disk | 48.95x | 781.708 ms | 12.4966 ms | 10.4352 ms | 780.188 ms |
MySql | 52.16x | 833.046 ms | 16.5805 ms | 28.1550 ms | 837.304 ms |
Here we see overhead cost of multiple I/O calls being significantly slower than an In Memory SQLite database whilst PostgreSQL continues to be a standout performer vs other RDBMS's.
Linux Benchmarks Takeaways​
It's not clear if SQL Server's poor performance is due to having a suboptimal Linux port, running from Docker or running in a Linux VM with only 7GB RAM.
Either way if you're running SQL Server on Linux you may want to consider running your own load tests to check you're not getting poor performance in your environment and whether or not you're better off running SQL Server on a Windows instance.
Intel iMac 5K Benchmarks​
The Intel iMac 5K / 24GB RAM benchmarks were run on Windows 10, specifications reported by BenchmarkDotNet:
BenchmarkDotNet v0.13.6, Windows 10 (10.0.19045.3208/22H2/2022Update)
Intel Core i7-7700K CPU 4.20GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
.NET SDK 7.0.203
[Host] : .NET 6.0.20 (6.0.2023.32017), X64 RyuJIT AVX2
Job-ILIRNE : .NET 6.0.20 (6.0.2023.32017), X64 RyuJIT AVX2
With all benchmarks running against local databases, installed using their native Windows Installers.
Optimized Bulk Insert Performance​
These benchmarks below uses the default optimal Bulk Insert implementation for each RDBMS:
Inserting 1,000 Rows​
Database | Relative | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
SQLite Memory | 1x | 2.638 ms | 0.0675 ms | 0.1981 ms | 2.660 ms |
PostgreSQL | 1.71x | 4.507 ms | 0.1548 ms | 0.4442 ms | 4.431 ms |
SqlServer | 2.44x | 6.432 ms | 0.1342 ms | 0.3763 ms | 6.319 ms |
SQLite Disk | 3.75x | 9.884 ms | 0.2986 ms | 0.8226 ms | 9.814 ms |
MySqlConnector | 8.26x | 21.791 ms | 1.4271 ms | 4.0017 ms | 20.746 ms |
MySql | 9.08x | 23.952 ms | 1.3798 ms | 3.8234 ms | 22.884 ms |
Inserting 10,000 Rows​
Database | Relative | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
PostgreSQL | 1x | 17.796 ms | 0.3128 ms | 0.4486 ms | 17.743 ms |
SQLite Memory | 1.32x | 23.424 ms | 0.4573 ms | 0.5444 ms | 23.354 ms |
SqlServer | 3.0x | 53.330 ms | 0.7668 ms | 0.7173 ms | 53.499 ms |
SQLite Disk | 5.78x | 102.848 ms | 2.0194 ms | 5.2845 ms | 101.927 ms |
MySqlConnector | 8.07x | 143.624 ms | 6.7792 ms | 19.3413 ms | 139.740 ms |
MySql | 8.15x | 145.037 ms | 6.1655 ms | 17.7889 ms | 141.211 ms |
Inserting 100,000 Rows​
Database | Relative | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
SQLite Memory | 1x | 240.111 ms | 2.6344 ms | 2.3353 ms | 239.452 ms |
PostgreSQL | 1.10x | 265.161 ms | 12.6705 ms | 36.9604 ms | 261.027 ms |
SqlServer | 2.42x | 580.433 ms | 9.3144 ms | 7.7779 ms | 580.560 ms |
MySql | 2.98x | 714.550 ms | 13.6676 ms | 28.5294 ms | 707.452 ms |
MySqlConnector | 3.04x | 729.408 ms | 14.6735 ms | 43.2651 ms | 722.036 ms |
SQLite Disk | 4.37x | 1,049.120 ms | 20.9215 ms | 29.3290 ms | 1,045.173 ms |
Inserting 1,000,000 Rows​
Database | Relative | Mean | Error | StdDev |
---|---|---|---|---|
SQLite Memory | 1x | 2.573 s | 0.0508 s | 0.0624 s |
PostgreSQL | 1.51x | 3.895 s | 0.0769 s | 0.2067 s |
SqlServer | 2.31x | 5.934 s | 0.1002 s | 0.0888 s |
MySqlConnector | 2.56x | 6.585 s | 0.1253 s | 0.1287 s |
MySql | 2.64x | 6.803 s | 0.1339 s | 0.1920 s |
SQLite Disk | 4.33x | 11.146 s | 0.1228 s | 0.1149 s |
Here we see that PostgreSQL continue to take the Bulk Insert crown platform even on Windows and SQL Server is also a good performer now that it's running on its native Windows/Intel platform.
It also highlights a significant performance of SQLite disk write performance on Windows/x64 which may be due to how macOS implements fsync.
Relative performance of ARM vs Intel​
Whilst they're not directly comparable with their different configurations, their relative performance numbers provides some indication of how much faster my new Macbook Air M2 is compared to my primary Intel iMac 5K Desktop, even with the overhead of running RDBMS's from within Docker containers:
Relative performance for Bulk Insert of 100,000 records​
Database | M2 ARM | Intel i7-7700K |
---|---|---|
PostgreSQL | 1x | 2.92x |
SQLite Memory | 1x | 1.44x |
SQLite Disk | 1x | 5.25x |
MySqlConnector | 1x | 2.30x |
MySql | 1x | 2.30x |
SqlServer | 1.44x | 1x |
Where other than SQL Server's poor performance on Linux/Rosetta, the benchmarks run much faster on Apple M2 Macbook Air.
Multiple Inserts Rows Performance​
These benchmarks show the performance of executing Multiple Row Inserts in batches of 1000 - measuring the comparative performance of each RDBMS in executing large SQL Insert statements:
Last SQL Server result removed due to poor outlier performance
Inserting 1,000 Rows​
Database | Relative | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
SQLite Memory | 1x | 2.710 ms | 0.0726 ms | 0.2084 ms | 2.745 ms |
PostgreSQL | 2.68x | 7.270 ms | 0.1439 ms | 0.2874 ms | 7.192 ms |
SQLite Disk | 3.67x | 9.944 ms | 0.3215 ms | 0.8692 ms | 9.754 ms |
MySqlConnector | 7.70x | 20.869 ms | 1.2050 ms | 3.4380 ms | 19.956 ms |
MySql | 8.86x | 24.016 ms | 1.3810 ms | 3.9845 ms | 22.311 ms |
SqlServer | 28.04x | 75.994 ms | 1.3113 ms | 1.5610 ms | 76.456 ms |
Inserting 10,000 Rows​
Database | Relative | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
SQLite Memory | 1x | 25.784 ms | 0.6895 ms | 2.0005 ms | 26.021 ms |
PostgreSQL | 2.49x | 64.107 ms | 1.2164 ms | 1.0783 ms | 64.225 ms |
SQLite Disk | 4.09x | 105.551 ms | 2.3293 ms | 6.7579 ms | 103.989 ms |
MySqlConnector | 7.14x | 184.193 ms | 4.3627 ms | 12.4471 ms | 180.157 ms |
MySql | 8.43x | 217.394 ms | 5.6931 ms | 16.6968 ms | 214.477 ms |
SqlServer | 31.80x | 819.829 ms | 16.1172 ms | 38.3043 ms | 829.895 ms |
Inserting 100,000 Rows​
Database | Relative | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
SQLite Memory | 1x | 246.285 ms | 3.9630 ms | 3.3093 ms | 246.185 ms |
PostgreSQL | 2.12x | 522.203 ms | 9.1596 ms | 8.1198 ms | 519.607 ms |
SQLite Disk | 4.26x | 1,048.217 ms | 17.4291 ms | 14.5541 ms | 1,044.868 ms |
MySqlConnector | 5.52x | 1,359.005 ms | 26.2161 ms | 29.1391 ms | 1,360.109 ms |
MySql | 6.73x | 1,656.828 ms | 32.2114 ms | 49.1902 ms | 1,649.606 ms |
SqlServer | 34.75x | 8,558.850 ms | 89.6498 ms | 83.8585 ms | 8,582.563 ms |
Inserting 1,000,000 Rows​
Database | Relative | Mean | Error | StdDev |
---|---|---|---|---|
SQLite Memory | 1x | 2.332 s | 0.0288 s | 0.0255 s |
PostgreSQL | 2.61x | 6.098 s | 0.1199 s | 0.1559 s |
SQLite Disk | 4.76x | 11.099 s | 0.1153 s | 0.1022 s |
MySqlConnector | 5.92x | 13.800 s | 0.1668 s | 0.1393 s |
MySql | 6.7x | 15.546 s | 0.3095 s | 0.3801 s |
SqlServer | 33.11x | 77.221 s | 0.5015 s | 0.4445 s |
PostgreSQL continues to shine here, significantly out performing other distributed RDBMS's for processing large, multi-row INSERT statements whilst SQL Server has a surprisingly poor showing on its native Windows/Intel platform - likely the result of a naive unoptimized implementation for these types of large SQL INSERT statements.
Single Insert Performance​
This benchmark measures the performance of multiple single inserts (i.e. when Bulk Insert is not available):
Database | Relative | Mean | Error | StdDev |
---|---|---|---|---|
SQLite Memory | 1x | 12.30 ms | 0.244 ms | 0.453 ms |
PostgreSQL | 37.84x | 465.43 ms | 9.082 ms | 10.812 ms |
SqlServer | 47.34x | 582.33 ms | 8.988 ms | 8.408 ms |
MySqlConnector | 346.47x | 4,261.57 ms | 81.441 ms | 96.950 ms |
MySql | 348.20x | 4,282.89 ms | 83.393 ms | 99.273 ms |
SQLite Disk | 619.59x | 7,620.93 ms | 89.152 ms | 83.393 ms |
We hope these results have been informative and have highlighted opportunities for improvements in your own systems needing to perform large inserts or data imports.
Feel free to leave any feedback or suggest improvements or other environments to run these benchmarks on in the comments.