Key takeaways

  • This major update to the MariaDB Python Connector is a total ground-up rewrite that makes it much easier to install and significantly faster to run. Here are the key takeaways from the version 2.0 release:
  • Simplified installation eliminates the old requirement for pre-installed C libraries, offering a pure Python version that works anywhere and a “plug-and-play” binary option for maximum speed.
  • Native async/await support and full type hinting allow the connector to fit perfectly into modern Python frameworks like FastAPI and Starlette without extra workarounds.
  • Massive performance gains make this the fastest MariaDB driver available, with speeds up to 64 times faster than competitors when handling complex data and large-scale queries.
  • Modern connection features like standard URI strings (mariadb://...) and intelligent statement caching simplify your code while automatically reducing database overhead.

Today’s release candidate for MariaDB Connector/Python 2.0 marks a transition from the architecture used in version 1.1. Although the previous version was fast, it came with technical friction – specifically the requirement for a pre-installed C/C++ connector, a lack of async and type hint support, and CPython-only compatibility.
In version 2.0, we’ve rebuilt the connector to remove those dependencies and add the missing features, all while making the underlying C extension faster. We invite the community to test this preview before the final 2.0.0 release.

Get Started

pip install mariadb[binary,pool]

Why a Major Version?

Three forces drove the rewrite:

  1. The Python ecosystem moved on. async/await is mainstream, type hints are expected, and URI-based configuration is standard. The 1.1 API predates all of this.
  2. Installation was a barrier. Requiring users to install the MariaDB C/C Connector before pip install mariadb was a constant source of friction, especially in containers and CI pipelines.
  3. The performance had headroom. The 1.1 C extension was already fast, but internal data structures — particularly metadata handling and parameter binding — left significant performance on the table.

What’s New

Flexible Distribution (CONPY-324)

Version 1.1 had a single installation path: compiling the C extension against a locally installed MariaDB C Connector. If the C library wasn’t present, pip install failed.

Version 2.0 introduces three mutually exclusive backends, plus an optional add-on:

ExtraWhat you getRequirements
(none)Pure Python implementationNothing
works everywhere, any interpreter
[c]C extensionLocal MariaDB C Connector + compiler
[binary]Pre-compiled C extension wheelNothing
compiler and C Connector bundled
[pool]Connection pooling add-onCan be combined with any backend above

Pick your backend:

pip install --pre mariadb # Pure Python -- CPython, PyPy, any platform 
pip install --pre mariadb[c] # C extension -- maximum performance
pip install --pre mariadb[binary] # Pre-compiled wheel -- performance, no setup

Add connection pooling to any of the above:

pip install --pre mariadb[pool] 
pip install --pre mariadb[c,pool]
pip install --pre mariadb[binary,pool]

All backends share the same API 

Note: --pre is required for now, as 2.0 is currently a release candidate.

Dedicated Connection Pool Package (CONPY-324) – Breaking Change

The pool package was fully rewritten for 2.0: it now works with both the pure Python and C extension implementations, and adds a new asynchronous API that did not exist in 1.1. Extracting it into a separate package reflects its expanded scope and keeps it optional for applications that do not need pooling.

Before (1.1):

pip install mariadb # pooling included automatically

After (2.0):

pip install mariadb[pool] # explicit opt-in for pooling

The API itself is unchanged, only the installation step differs. The pool package is deliberately optional: applications that never use connection pooling no longer carry the dependency.

Synchronous pool:

import mariadb

pool = mariadb.create_pool(
host="localhost",
port=3306,
user="user",
password="password",
database="mydb",
min_size=5,
max_size=10,
ping_threshold=0.25
)

with pool.acquire() as conn:
with conn.cursor() as cursor:
cursor.execute("SELECT COUNT(*) FROM users")
count = cursor.fetchone()[0]
print(f"Total users: {count}")

Asynchronous pool:

import asyncio
import mariadb

async def main():
pool = await mariadb.create_async_pool(
host="localhost",
user="user",
password="password",
database="mydb",
min_size=5,
max_size=10
)

async with await pool.acquire() as conn:
async with conn.cursor() as cursor:
await cursor.execute("SELECT * FROM users WHERE id = ?", (1,))
row = await cursor.fetchone()
print(f"User: {row}")

await pool.close()

asyncio.run(main())

Native Async Support (CONPY-2, CONPY-327)

Version 1.1 had no async support. Applications using asyncio, FastAPI, or Starlette had to wrap synchronous calls in thread pools, adding latency and complexity.

Version 2.0 provides first-class async/await support for both the pure Python and C extension implementations:

import asyncio
import mariadb

async def main():
# Single connection
async with await mariadb.asyncConnect("mariadb://user:pass@host/db") as conn:
async with conn.cursor() as cursor:
await cursor.execute("SELECT * FROM users WHERE id = ?", (1,))
row = await cursor.fetchone()
print(row)

# With connection pool (requires mariadb[pool])
pool = await mariadb.create_async_pool(
"mariadb://user:pass@host/db", min_size=1, max_size=20
)
async with await pool.acquire() as conn:
async with conn.cursor() as cursor:
await cursor.execute("SELECT 1")
result = await cursor.fetchone()
await pool.close()

asyncio.run(main())

The pure Python async path uses native asyncio socket I/O — no threads, no GIL contention. The C extension async path uses libmariadb’s non-blocking API, yielding to the event loop during network waits while keeping C-speed parsing.

Connection URI Support (CONPY-326)

Version 1.1 required keyword arguments for every connection parameter: 

# 1.1 -- verbose
conn = mariadb.connect(host="localhost", user="root", password="secret", database="mydb")

Version 2.0 supports standard URI syntax:

# 2.0 -- concise
conn = mariadb.connect("mariadb://root:secret@localhost:3306/mydb")

URIs make configuration simpler: a single DATABASE_URL environment variable replaces multiple connection parameters. Keyword arguments still work for backward compatibility and can override URI values when both are provided.

Comprehensive Type Hints (CONPY-325)

Every public API surface is now fully annotated. mypy –strict and pyright pass cleanly. IDEs provide accurate autocomplete, and type errors are caught at development time rather than in production.

conn: mariadb.Connection = mariadb.connect(
host="localhost",
port=3306,
user="root",
password="secret",
database="mydb"
)

cursor: mariadb.Cursor = conn.cursor()
cursor.execute("SELECT * FROM users WHERE id = ?", (1,))
row: tuple | None = cursor.fetchone()
rows: list[tuple] = cursor.fetchall()
cursor.close()
conn.close()

Unified Binary Protocol Control (CONPY-338)

In 1.1, the C extension silently auto-promoted certain parameter types (bytes, datetime, etc.) to the binary protocol, even when the user expected text protocol.

Version 2.0 makes protocol selection explicit and consistent across both implementations:

  • Text protocol by default: predictable, debuggable
  • binary=True at connection or cursor level for prepared statements
  • Dict parameters always use text protocol (named parameter substitution)
# Text protocol (default)
cursor.execute("SELECT ?", (b'\xde\xad',))
# sends: SELECT _binary'\xde\xad'

# Binary protocol (explicit)
cursor = conn.cursor(binary=True)
cursor.execute("SELECT ?", (b'\xde\xad',))
# uses COM_STMT_PREPARE + COM_STMT_EXECUTE

No more hidden protocol switches. The behavior is identical whether you use the pure Python or C implementation.

Performance: Measured, Not Claimed

Every architectural change was benchmarked. All results below were measured on the same machine with pytest-benchmark (1000+ rounds). Results are in operations per second (higher is better).

C Extension: 1.1 vs 2.0 (Text Protocol)

Both 1.1 and 2.0 use the same text protocol path (COM_QUERY), so these benchmarks are directly comparable:

Benchmark1.1 (ops/s)2.0 C (ops/s)Change
DO 165,78970,922+8%
SELECT 134,36439,683+15%
SELECT 100 columns14,10414,124~0%
DO 1000 params2,1173,239+53%

Text parameter binding is 53% faster on 1000 parameters and simple queries are 8–15% faster. Wide result sets remain on par.

What Drove the Text Protocol Improvement

  • Columnar metadata storage (CONPY-335): replaced the traditional array-of-objects layout with parallel arrays, eliminating per-column Python object dereferencing during row parsing and improving cache locality.
  • C-level metadata reading (CONPY-333): moved column definition parsing from Python into C. In 1.1, the C extension was actually slower than the pure Python connector on wide binary result sets due to metadata overhead. This fix eliminated that anomaly.

New in 2.0: Prepared Statement Caching

Version 1.1 had no statement caching — every binary protocol execution required a full COM_STMT_PREPARE + COM_STMT_EXECUTE + COM_STMT_CLOSE round-trip. To show the impact fairly, here are three columns: text protocol, binary protocol without caching (equivalent to 1.1’s behavior), and binary protocol with caching (2.0 default):

BenchmarkText (ops/s)Binary no cacheBinary cachedCache speedup
DO 1000 params3,1522,1754,4722.1×
SELECT 1000 rows4,5714,5004,9431.1×
SELECT 100 columns11,86210,00016,9201.7×

Without caching, the binary protocol is actually slower than text on parameter-heavy queries (2,175 vs 3,152 ops/s) — the round-trip overhead of PREPARE + EXECUTE + CLOSE exceeds any benefit from server-side parsing. This was the situation in 1.1.

Version 2.0’s prepared statement cache eliminates this overhead. MYSQL_STMT* handles are reused across executions of the same SQL using a checkout/return pattern at the connection level. Combined with MariaDB’s MARIADB_CLIENT_CACHE_METADATA capability, the binary protocol now outperforms text across the board.

Configuration

Caching is controlled by two connection options:

  • cache_prep_stmts (default: True) — enables or disables the prepared statement cache.
  • prep_stmt_cache_size (default: 100) — maximum number of cached statements per connection. When the cache is full, the least recently used statement is evicted.

# Default: caching enabled with up to 100 statements
conn = mariadb.connect("mariadb://localhost/mydb")

# Larger cache for applications with many distinct queries
conn = mariadb.connect("mariadb://localhost/mydb?prep_stmt_cache_size=500")

# Disable caching entirely (1.1-like behavior)
conn = mariadb.connect("mariadb://localhost/mydb?cache_prep_stmts=false")

When to use binary protocol

The cache benefits applications that repeatedly execute the same SQL statements — the first execution pays the PREPARE cost, but all subsequent executions skip it entirely.

  • Connection-level (binary=True): best when the application mostly runs the same set of parameterized queries. All cursors default to binary protocol, and the cache handles the rest. 
conn = mariadb.connect("mariadb://localhost/mydb?binary=true")
cur = conn.cursor()
cur.execute("SELECT * FROM users WHERE id = ?", (user_id,)) # binary
  • Per-cursor (cursor(binary=True)): best for mixed workloads where only specific hot queries benefit from binary protocol.

conn = mariadb.connect("mariadb://localhost/mydb")

# Hot path -- binary with caching
cur = conn.cursor(binary=True)
cur.execute("SELECT * FROM users WHERE id = ?", (user_id,))

# Ad-hoc query -- text protocol, no PREPARE overhead
cur2 = conn.cursor()
cur2.execute("SHOW PROCESSLIST")

Synchronous: vs the Competition

How does 2.0 compare to the most popular Python MySQL/MariaDB drivers? We benchmarked against PyMySQL 1.1.2 (pure Python) and mysql-connector-python 9.6.0 (Oracle’s C extension and pure Python variants):

Benchmarkmariadb_c 2.0 (ops/s)mariadb 2.0
(ops/s)
PyMySQL 1.1.2 (ops/s)mysql-conn (c )  (ops/s)mysql-conn (pure) (ops/s)
DO 168,493 62,11262,50023,36415,649
SELECT 153,763 33,11330,86416,36710,799
SELECT 1000 rows (text)4,831 8024161,188272
SELECT 1000 rows (binary)5,089 6801,04122
SELECT 100 cols (text)14,065 4,9751,9524,3101,791
SELECT 100 cols (binary)21,231 10,8462,86624
DO 1000 params (text)3,272 3,1421,6812,300775
DO 1000 params (binary)4,533 2,1337117
Batch INSERT5,876 4,4861,7841,7911,455

mariadb_c wins all 9 benchmarks. The gaps are huge on data-heavy workloads:

  • SELECT 1000 rows: mariadb_c delivers 4,831 ops/s vs mysql-connector (C)’s 1,188 and PyMySQL’s 416 — 4× and 12× faster
  • Binary protocol params: mariadb_c at 4,533 ops/s vs mysql-connector (C)’s 71 — 64× faster. Oracle’s prepared statement implementation has severe overhead at high parameter counts
  • 100-column binary: mariadb_c at 21,231 ops/s vs mysql-connector pure Python’s 24 — a 895× difference

Even the pure Python mariadb 2.0 holds its own: it matches or beats mysql-connector (C) on parameter binding and is 2–3× faster than PyMySQL on most benchmarks.

Asynchronous: vs aiomysql and asyncmy

Version 2.0 introduces native async support — so how does it compare to the existing async MySQL drivers, aiomysql (async wrapper around PyMySQL) and asyncmy (async client with C speedups)?

Benchmarkmariadb_c async (ops/s)mariadb async (ops/s)Aiomysql (ops/s)Asyncmy (ops/s)
DO 121,78624,631 24,33121,552
SELECT 121,142 17,39115,97414,265
SELECT 1000 rows1,799 765379339
SELECT 100 cols10,515 4,3651,7641,938
DO 1000 params2,899 2,8082,0332,142
Batch INSERT5,444 3,7381,6501,617

mariadb_c async wins 5 out of 6 benchmarks, with mariadb async taking DO 1. Key results:

  • SELECT 1000 rows: mariadb_c async at 1,799 ops/s vs aiomysql’s 379 and asyncmy’s 339 — 4.7× and 5.3× faster
  • SELECT 100 cols: mariadb_c async at 10,515 ops/s vs aiomysql’s 1,764 — 6× faster
  • Batch INSERT: both mariadb variants are 3× faster than aiomysql/asyncmy
  • Pure Python mariadb async is 2× faster than aiomysql on SELECT 1000 rows

For FastAPI, Starlette, or any asyncio-based application, mariadb 2.0 delivers async performance that no other Python MySQL/MariaDB driver can match, with or without the C extension.

Upgrading from 1.1

The mariadb.connect() API remains largely backward compatible — most keyword-argument connections work without changes. However, several options have been removed, changed, or added.

Removed Options

  • reconnect / auto_reconnect removed: In 1.1, setting reconnect=True enabled automatic reconnection on lost connections. This was silently hiding failures and causing unpredictable behavior: lost session state, uncommitted transactions, broken transaction isolation. In 2.0, there is no automatic reconnection. The reconnect() method still exists for explicit manual reconnection, but the automatic behavior is gone. For production use, a connection pool is the recommended approach.
  • plugin_dir removed in pure Python: The C extension still supports it, but the pure Python driver does not load native authentication plugins from disk.
  • cursor_type removed: In 1.1, cursor_type=CURSOR.READ_ONLY opened a server-side cursor. In 2.0, use buffered=False instead: this reads rows from the network on demand rather than fetching the entire result set upfront.
  • prepared removed: In 2.0, use binary=True instead: it provides the same prepared statement behavior with a clearer name.

Changed Behavior

  • binary is now a connection-level option: In 1.1, binary=True could only be set per cursor. In 2.0, you can set it at connection level to default all cursors to the binary protocol:
# 2.0 -- all cursors use binary protocol by default
conn = mariadb.connect("mariadb://root:secret@localhost/mydb?binary=true")
  • No more automatic protocol promotion: In 1.1, passing bytes, datetime, or similar types as parameters silently switched the cursor to binary protocol. In 2.0, the text protocol is always used unless binary=True is explicitly set.
  • pip install mariadb now installs pure Python: For the C extension, use pip install mariadb[c]. For pre-compiled wheels, use pip install mariadb[binary].
  • Connection pooling is now a separate package: Use pip install mariadb[pool] to get pooling support. In 1.1, pooling was built into the mariadb package.

New Options

  • URI connection strings: mariadb.connect(“mariadb://user:pass@host/db”) in addition to keyword arguments.
  • binary at connection level: defaults all cursors to prepared statements.
  • cache_prep_stmts / prep_stmt_cache_size: controls the prepared statement cache (C extension).
  • pipeline: enables pipelining for batch operations.
  • Async API: AsyncConnection, AsyncCursor, asyncConnect(), AsyncConnectionPool are entirely new.

Summary

MariaDB Connector/Python 2.0 is faster, more flexible, and more Pythonic than 1.1:

  • Pure Python option: install anywhere, no compiler required
  • Native async/await: for both pure Python and C implementations
  • URI connections: standard mariadb:// scheme
  • Full type hints: mypy –strict compatible
  • 2× faster parameter binding and 2× faster wide result sets in the C extension
  • Explicit protocol control: no hidden behavior differences between implementations