Something changed about who’s querying your database
For decades, database clients were predictable: application servers running prepared statements through connection pools, ORMs generating SQL from object mappings, analysts writing queries by hand. The access patterns were well-understood, and thirty years of benchmarking literature (TPC-C, TPC-H, sysbench) told you exactly how your database would perform under those workloads.
That’s no longer the only story. AI agents are becoming database clients. Tools like Claude, GPT, and Cursor now connect to databases at runtime — not through hardcoded queries, but through protocols that let the agent discover your schema, decide what to query, and interpret the results on the fly. The most common of these is MCP (Model Context Protocol), an open standard that gives agents structured access to external systems, including databases.
This creates a workload profile that looks nothing like a traditional web application:
- Schema discovery at session start — agents inspect tables and columns before issuing a single query
- Sequential single-row CRUD calls — not batched prepared statements, but one tool call at a time
- Filter patterns decided at runtime — the agent chooses which columns to filter on based on the user’s question, not a predetermined query plan
- A protocol layer between agent and database — HTTP transport, JSON-RPC serialization, and middleware translation on every call
There are no public benchmarks for this. TPC-C doesn’t measure schema discovery latency. Sysbench doesn’t test what happens when every query passes through a JSON-RPC envelope. Nobody has published data on how PostgreSQL, MySQL, SQL Server, and SQLite compare when the client is an AI agent speaking MCP instead of an application speaking SQL.
We wanted that data. So we built the benchmark.
Transparency note: We built Faucet, the MCP server used as the test harness. We chose it because it provides an identical MCP interface to all four databases — making a fair comparison possible. All raw data, harness code, and methodology are published so you can verify independently or run it yourself.
What we tested
We loaded identical datasets into PostgreSQL, MySQL, SQL Server, and SQLite. We connected them all to an MCP server and fired millions of tool calls through the Model Context Protocol — the same way AI agents query databases in production.
Then we added indexes and tuned each database according to best practices. The results challenged common assumptions.
Setup
Hardware: AWS EC2 c6a.4xlarge (16 vCPU AMD EPYC, 32 GB RAM) in us-east-1.
Databases: PostgreSQL 17, MySQL 8, and SQL Server 2022 — each in a Docker container limited to 4 CPUs and 8 GB RAM. SQLite ran in-process within the MCP server on the host machine (see Caveats for resource implications).
Dataset: 2.56 million rows across 8 normalized tables (customers, products, orders, order items, reviews, categories, inventory, suppliers) with realistic distributions and foreign key relationships.
MCP Harness: A custom Go client implementing the full MCP lifecycle — initialize → tools/list → tools/call — over JSON-RPC 2.0 with Streamable HTTP transport. Every single operation is a real MCP tool call through Faucet, which provides an identical MCP interface to all four databases.
Protocol: Each benchmark operation follows the exact path an AI agent would take: HTTP POST → JSON-RPC envelope → Faucet MCP server → database query → JSON-RPC response. No shortcuts, no raw SQL bypass.
Methodology: 3 iterations per workload, median selected. Two rounds: baseline (default configs) and optimized (indexes + tuning per each database’s official documentation). This article covers read workloads: point reads, filtered queries, analytical patterns, and concurrency scaling. Write benchmarks were also collected and will be covered in a follow-up. Full methodology and raw data: github.com/faucetdb/mcp-db-benchmark.
Finding 1: Indexing Your Database Improves Performance 9-74x. Switching Databases Gains You 2-4x.
This is the headline result. If you’re deciding where to invest engineering effort — picking a different database engine or indexing the one you already have — the data overwhelmingly favors indexing.
Our filtered query workload fires MCP tool calls like these against each database:
tools/call: faucet_query(table: "orders", filter: "customer_id = 42817", limit: 10)
tools/call: faucet_query(table: "products", filter: "category_id = 7", limit: 20)
tools/call: faucet_query(table: "reviews", filter: "product_id = 19433", limit: 20)
These filter on foreign key columns — not primary keys. Without secondary indexes, the database must scan the entire table for each query. With indexes, it’s a direct lookup.

Before adding indexes, the fastest database (MySQL at 1,238 ops/s) was 48x faster than the slowest (SQLite at 26 ops/s) on filtered queries. That looks like database choice matters enormously.
But after adding appropriate indexes to each database:
| Database | Before Indexing | After Indexing | Improvement |
|---|---|---|---|
| SQLite* | 26 ops/s | 1,931 ops/s | 74.5x |
| PostgreSQL | 78 ops/s | 1,038 ops/s | 13.3x |
| SQL Server | 60 ops/s | 517 ops/s | 8.7x |
| MySQL | 1,238 ops/s | 1,234 ops/s | 1x (see below) |
*SQLite ran with more host resources than the containerized server databases. See Caveats.
The gap between the best and worst database narrowed to 3.7x. The gap created by a missing index was 9-74x.
The same pattern held for analytical queries: SQLite improved 52x, PostgreSQL 8.2x, SQL Server 7.5x. In every case, adding indexes to the same database delivered a far larger improvement than switching to a different database would have.
Finding 2: MySQL’s Defaults Are Secretly Excellent
Look at MySQL’s column in the chart above. Before and after optimization: virtually identical. Here’s the full picture across every read workload:

| Database | Point Reads | Filtered Queries | Analytical |
|---|---|---|---|
| PostgreSQL | 1.0x | 13.3x | 8.2x |
| MySQL | 1.0x | 1.0x | 1.0x |
| SQL Server | 0.6x* | 8.7x | 7.5x |
| SQLite* | 1.0x | 74.5x | 51.9x |
*SQL Server point reads regressed after optimization due to Query Store monitoring overhead — a known tradeoff when enabling query analytics on sub-millisecond operations.
MySQL sits at 1.0x across every workload. The question you should be asking: how can MySQL match its indexed performance without explicit indexes on those foreign key columns?
The answer is a combination of InnoDB architecture features working together. InnoDB stores all table data in a clustered index ordered by primary key, which means related rows are physically co-located on disk — making full table scans highly cache-friendly. Its buffer pool aggressively caches these pages in memory, and with our 2.56M-row dataset fitting entirely within the 6 GB pool, repeat scans hit warm memory every time. On top of this, InnoDB’s Adaptive Hash Index (AHI) automatically builds in-memory hash structures on frequently accessed B-tree pages, further accelerating repeated lookups. During the benchmark’s warmup phase and first iteration, these mechanisms collectively self-optimized for the workload’s access patterns. By the time measurements stabilized, MySQL was scanning in-memory pages at near-CPU speed.
You can see this in the raw data: MySQL’s first iteration of Round 1 ran at 562 ops/s (before AHI warmed up), while iterations 2 and 3 hit 1,234 ops/s. The benchmark uses median selection, so the reported number reflects the warm state.
An important caveat: our dataset (2.56M rows) fits entirely within MySQL’s 6 GB buffer pool. AHI works on in-memory pages — at larger-than-memory scale, where not all pages stay resident, the gap between “no explicit indexes” and “properly indexed” would almost certainly widen. MySQL’s self-tuning may be less effective when it can’t keep the working set cached. We’d expect explicit indexes to matter more for MySQL at scale.
That said, the practical takeaway still holds: InnoDB’s defaults — clustered primary key storage, adaptive hash indexing, query plan caching — already cover the access patterns that MCP agents generate. PostgreSQL, SQLite, and SQL Server all needed explicit indexing to reach comparable throughput. MySQL got there on its own, at least at this scale.
PostgreSQL is the most common choice for new projects, and for good reason — its extensibility and analytical capabilities are unmatched. But for MCP workloads — where queries are structured CRUD operations generated by agents, not hand-tuned analytical SQL — MySQL’s out-of-the-box behavior required the least effort.
Finding 3: MCP Protocol Overhead Is Sub-Millisecond
The most common objection to MCP is “it adds unnecessary overhead.” Here’s what we measured:

Every database responds to point-read MCP tool calls with a median latency under 1.2ms. That includes the full round trip: HTTP transport, JSON-RPC 2.0 parsing, Faucet query translation, database execution, and response serialization.
SQLite — which runs in-process with zero network hop — clocks 0.32ms. The difference between SQLite and the server databases (0.3-0.8ms) approximates the network + connection overhead that MCP adds. It’s real, but it’s noise compared to actual query execution time.
Session initialization (the initialize + tools/list handshake that every MCP client performs on connect) takes 0.7-3.7ms depending on the database. An agent “boots up” against any of these databases in under 4ms.
If you’ve been avoiding MCP because of performance concerns, the data says you can stop worrying.
Finding 4: The Optimization Floor
Here’s something we didn’t expect to find. After optimization, all four databases converge in latency as queries get faster:

For session discovery (the slowest operation), there’s a 5x spread between databases. For point reads (the fastest), the spread compresses to 3.5x — and three of the four databases cluster within a millisecond of each other.
Why this happens: Once a query executes in sub-millisecond time, the MCP protocol layer (HTTP + JSON-RPC + serialization) becomes a proportionally larger share of total latency. Further database optimization yields diminishing returns because you’re optimizing below the protocol floor.
This is a concept that only exists when you measure through MCP rather than direct database connections. It means there’s a practical ceiling on how much database tuning helps for MCP workloads — and most databases hit it after basic indexing.
Finding 5: Concurrency Reveals the Middleware
We scaled from 1 to 50 simultaneous MCP agents, each maintaining its own session:

| Database | 1 Agent | 10 Agents | 50 Agents | Scale Factor |
|---|---|---|---|---|
| SQLite* | 2,252 ops/s | 9,239 ops/s | 9,443 ops/s | 4.2x |
| MySQL | 1,530 ops/s | 8,681 ops/s | 10,371 ops/s | 6.8x |
| PostgreSQL | 1,187 ops/s | 6,227 ops/s | 6,226 ops/s | 5.2x |
| SQL Server | 1,004 ops/s | 4,355 ops/s | 3,494 ops/s | 3.5x |
Two things stand out:
PostgreSQL plateaus at 10 agents. Going from 10 to 50 concurrent agents produces zero additional throughput. This isn’t a PostgreSQL limitation — PostgreSQL handles far more concurrent connections in production. The bottleneck is in the middleware layer: Faucet uses different Go database drivers for each database (pgx for PostgreSQL, go-sql-driver/mysql for MySQL, go-mssqldb for SQL Server). Each driver has different connection pooling behavior, wire protocol efficiency, and concurrency characteristics. The PostgreSQL driver’s connection handling saturates at a lower concurrency level than MySQL’s driver does in this configuration.
MySQL has a reproducible anomaly at exactly 25 agents (visible as the dip in the chart) where throughput collapses from 8,681 ops/s to 3,126 ops/s and ~16% of requests fail with connection errors. At c=50 it recovers to 10,371 ops/s with a residual 0.16% error rate. This appeared across multiple benchmark runs and is a middleware connection pooling issue, not a MySQL limitation. MySQL’s c=50 throughput should be read with this context.
The takeaway for anyone building multi-agent MCP architectures: your MCP server’s connection management — including driver choice and pool configuration — will become your scaling bottleneck before the database does. Tune the middleware, not just the database.
Caveats
We want to be upfront about limitations:
- SQLite ran with more host resources than the containerized server databases. Its absolute throughput numbers benefit from both its in-process architecture (a real advantage) and unequal resource allocation (an unfair one). The relative patterns — indexing impact, optimization floor convergence — are valid regardless.
- This article covers reads only. Write benchmarks were collected but excluded from this analysis due to a harness bug affecting 3 of 4 databases (detailed in the repo). A write-focused follow-up is planned.
- 2.56M rows fits in memory for all databases at 8 GB per container. These results reflect in-memory query processing + MCP overhead, not disk I/O behavior. Larger-than-memory datasets would shift the balance toward databases with better buffer management.
- Single MCP server instance. Production deployments would typically run multiple instances behind a load balancer. Our concurrency findings reflect single-process limits.
- Faucet as harness. We used Faucet because it provides identical MCP tool interfaces to all four databases, making the comparison fair. A different MCP server implementation would produce different absolute numbers, though the relative patterns should hold.
Recommendations
Based on these findings, here’s our practical guidance for building MCP-connected agent systems:
-
Index first, choose databases second. A missing index costs you 9-74x. Switching databases gains or loses you 2-4x. The math is clear.
-
Start with MySQL if you want zero tuning. Its defaults are already optimized for the CRUD patterns that MCP agents generate. You can always add PostgreSQL’s advanced features later if you need them.
-
Consider SQLite for single-agent, read-heavy workloads. Our SQLite results benefited from more host resources (see Caveats), but the architectural advantage — zero network hop, no connection overhead — is real and well-documented independently. If your use case is one agent querying a local dataset, SQLite through MCP deserves serious consideration.
-
Don’t fear MCP overhead. Sub-millisecond protocol cost. The data is clear: MCP is not your bottleneck.
-
Budget for middleware tuning at scale. If you’re running 10+ concurrent agents, your MCP server’s connection pool configuration matters more than your database tuning.
Reproduce It
Everything — the harness code, Docker Compose, dataset generator, optimization scripts, raw JSON results, and analysis — is open source:
github.com/faucetdb/mcp-db-benchmark
Clone it, spin up a VM, and run it yourself. If your results differ from ours, we want to know.
This benchmark was conducted using Faucet, an open-source MCP server that provides a uniform interface to PostgreSQL, MySQL, SQL Server, SQLite, Oracle, Snowflake, and MariaDB.