# ARX.SDK on MySQL: benchmarks

A living summary of the MySQL benchmarks: how ARX compares with raw MySqlConnector, Dapper and EF Core.
**Update it whenever a benchmark is rerun or an operation is added.**

| | |
|---|---|
| Last updated | 2026-11-09: first full run of every operation (106 benchmarks, 32 minutes), then Execute rerun after ARX's own `CALL` by name |
| Operations measured | Create, CreateMultiple, Update, UpdateMultiple, Delete, DeleteMultiple, Retrieve, RetrieveMultiple (rows, a page with the total), Execute (a procedure with result rows and an OUT value, a scalar function) |
| Machine | Intel Core i7-8750H, Windows 11, .NET 10.0.14, MySQL 8.4.13 (`mysql:8.4`) in Docker Desktop, MySqlConnector 2.6.2, BenchmarkDotNet 0.15.8, job Standard (8 iterations, 3 warm-ups) |
| Compared with | Raw MySqlConnector (hand-written, typed parameters), Dapper 2.1.89, EF Core 9 through Pomelo 9.0.0 (the latest Pomelo; it has no EF Core 10 release yet), `MySqlBulkCopy` (`LOAD DATA LOCAL INFILE`, with `local_infile` turned on for the run) |
| Reports | The full run: results/2026-11-09-full-run/. Execute after the change: [results/2026-11-09-execute-after-own-call.md](#report-2026-11-09-execute-after-own-call). |

**In one line:** ARX is level with hand-written MySqlConnector for many-row writes and deletes, and far faster than
EF Core (19–37×) and Dapper's row-by-row list execute (14–190×) for many-row writes; level with Dapper for single-row calls; level with raw and Dapper for reads up
to 1,000 rows. Its gaps are a procedure call (1.1–1.2×) and large reads (1.36× a typed reader at 10,000 rows, level
with EF Core).

Read the ratios, not the absolute times, which depend on the machine. A single-row write here costs about 3 ms, nearly
all of it MySQL's commit in Docker Desktop; differences under about 15% on a single run are inside the noise. One run
of each benchmark so far: treat close calls as level.

## Every operation at a glance (2026-11-09)

Mean times. "Raw" is the hand-written MySqlConnector form (a multi-row `INSERT`, `UPDATE … JOIN (VALUES ROW …)`, one
`IN` list), the same shape of SQL ARX sends.

| Operation | **ARX** | Raw MySqlConnector | Dapper | EF Core | ARX vs raw |
|---|---|---|---|---|---|
| Create, one row | **3.85 ms** | 3.29 ms | 3.86 ms | 7.01 ms | 1.17× (level with Dapper) |
| CreateMultiple, 100 rows, ids back | **7.39 ms** | 6.59 ms | – | 138 ms | 1.12× |
| CreateMultiple, 1,000 rows, ids back | **39.4 ms** | 48.7 ms | – | 1.41 s | 0.83× |
| CreateMultiple, 10,000 rows, ids back | **426 ms** | 397 ms | – | 14.6 s | 1.09× |
| CreateMultiple, 10,000 rows, no ids needed | **459 ms** (still returns ids) | `MySqlBulkCopy` 152 ms | one statement a row: 28.5 s | – | 3.0× `MySqlBulkCopy` |
| Update, one row | **1.60 ms** | 1.49 ms | 1.33 ms | 2.35 ms | 1.08× |
| UpdateMultiple, 100 rows | **4.64 ms** | 4.05 ms | 66.7 ms | 27.4 ms | 1.15× |
| UpdateMultiple, 1,000 rows | **45.2 ms** | 44.1 ms | 681 ms | 329 ms | 1.04× |
| UpdateMultiple, 10,000 rows | **481 ms** | 436 ms | 6.93 s | 2.34 s | 1.15× |
| Delete, one row | **5.19 ms** | 5.36 ms | 5.27 ms | 13.3 ms | level |
| DeleteMultiple, 100 ids | **6.61 ms** | 6.03 ms | 6.46 ms | 14.1 ms | 1.10× |
| DeleteMultiple, 1,000 ids | **15.8 ms** | 14.9 ms | 15.8 ms | 22.7 ms | 1.09× |
| DeleteMultiple, 10,000 ids | **154 ms** | 144 ms | 160 ms | 112 ms | 1.07× (EF Core 0.78×) |
| Retrieve, one row by id | **1.50 ms** | 1.34 ms | 1.27 ms | 2.39 ms | 1.13× |
| RetrieveMultiple, 100 rows | **1.97 ms** | 1.93 ms | 2.01 ms | 2.40 ms | level |
| RetrieveMultiple, 1,000 rows | **5.29 ms** | 4.79 ms | 4.92 ms | 6.03 ms | 1.11× |
| RetrieveMultiple, 10,000 rows | **36.8 ms** | typed 27.1 ms | 28.4 ms | 38.6 ms | 1.36× typed; level with EF Core |
| A page of 50 with the total | **2.03–4.83 ms** | 1.78–4.58 ms | – | – | 1.05–1.14× |
| Execute: a procedure, 1 row + an OUT value | **4.25 ms** | 3.54 ms | 3.56 ms | no way to read OUT values | 1.20× (1.31× before the change) |
| Execute: a procedure, 1,000 rows | **5.44 ms** | 4.45 ms | 4.73 ms | – | 1.22× (1.33× before) |
| Execute: a scalar function | **1.45–1.63 ms** | 1.25–1.34 ms | – | – | 1.09–1.32× |

The Dapper CreateMultiple and UpdateMultiple columns are `ExecuteAsync(sql, list)`, one statement per row: what Dapper
does with a list. EF Core's numbers are Pomelo's batching (one statement per entity in a batch).

## What the numbers say

- **Many-row writes are level with hand-written multi-row SQL** (0.83–1.15×): ARX sends multi-row statements of up to
  1,000 rows and reads a whole statement's generated ids as one block, so returning ids in list order
  costs nothing extra. EF Core is 20–37× slower there, and Dapper's list execute 16–190×.
- **`MySqlBulkCopy` is 3.0× faster for 10,000 rows when ids aren't needed.** It needs `local_infile` on the server and
  `AllowLoadLocalInfile` on the connection (both off by default, for safety), and returns no ids; ARX doesn't use it.
- **Single-row calls are level with Dapper** and 1.1–1.2× raw: ARX adds its session statement (UTC and a strict SQL mode) to the call's round trip and builds an entity; the commit dominates.
- **Deletes by id are level with raw and Dapper** at every size: MySQL has no parameter limit (MySqlConnector writes the
  values into the text) and searches a long `IN` list well, so one list of up to 10,000 ids a statement is the best
  form. EF Core's `Contains` is faster at 10,000 ids.
- **Reads are level with raw and Dapper up to 1,000 rows**; at 10,000 rows ARX's dictionaries cost 1.36× a typed reader
  (level with EF Core), as on PostgreSQL and SQL Server.
- **A procedure call by name** was 1.31–1.40×: MySqlConnector's own call needed a round trip of its own for ARX's
  session statement. ARX now writes the `CALL` itself, with the session statement, `SHOW WARNINGS` and the OUT values
  in one round trip: 1.12–1.22×. What is left is the `SHOW WARNINGS` that collects messages (about 0.15 ms, measured;
  `CollectMessages = false` skips it) and ARX's result building.
