Table of Contents

PHP RFC: Add MariaDB-specific features to mysqlnd and mysqli

Introduction

The PHP MySQL ecosystem is currently centered around mysqlnd, mysqli, and PDO_MySQL, which together provide a unified and efficient interface for interacting with MySQL-compatible database servers. MariaDB originated as a fork of MySQL and continues to share a large portion of its protocol, behavior, and feature set. Because of this substantial overlap, maintaining a completely separate PHP extension for MariaDB would introduce unnecessary duplication, fragmentation, and long-term maintenance cost.

At the same time, MySQL and MariaDB have diverged over the years. MariaDB has introduced a number of server-side features that are not available in MySQL, many of which provide significant performance and observability improvements. Currently, these MariaDB-specific capabilities are either inaccessible or only partially usable from PHP, despite being available through the same wire protocol and client library functions.

This RFC proposes to extend mysqlnd and mysqli to optionally expose MariaDB-specific functionality when connected to a MariaDB server, without affecting behavior when used with MySQL. The goal is not to fork or specialize the extensions, but to enhance them in a way that preserves compatibility while allowing PHP applications to benefit from MariaDB’s additional capabilities.

The MariaDB features targeted by this proposal include:

Exposing these features through existing PHP database extensions allows developers to take advantage of MariaDB’s strengths while continuing to rely on familiar MySQL-oriented APIs. This approach aligns with PHP’s long-standing emphasis on pragmatism, performance, and backward compatibility, while preparing the extensions to handle protocol divergence and feature evolution in MariaDB.

Proposal

Notes:

Detecting MySQL or MariaDB

A fundamental requirement for exposing MariaDB-specific features in PHP extensions is reliably detecting whether the connected server is MariaDB or MySQL. This cannot be done solely by checking the server_version string. While standard MySQL and MariaDB installations report the server type and version in server_version, many derived products—including cloud offerings, packaged distributions, and proxies may report different names, modified version numbers, or other inconsistencies. Therefore, relying on server_version alone is unreliable for feature detection.

A robust approach is to determine server type through capability flags exchanged between the client and server during the connection handshake. These flags indicate which features the server supports and which extensions the client can enable.

One key difference between MySQL and MariaDB is the handling of the long password capability:

MySQL servers always indicate the CLIENT_LONG_PASSWORD capability.

MariaDB historically used the same capability name, but later versions renamed it to CLIENT_MYSQL and removed it entirely.

By examining these flags, the client can detect MariaDB servers accurately and safely. Once a MariaDB server is recognized, the client can both read and advertise MariaDB-specific capabilities, enabling access to enhanced features.

To fully support modern MariaDB features, PHP extensions must handle 64-bit capability flags. These flags extend the feature set beyond what can fit in traditional 32-bit integers. For legacy 32-bit clients and to maintain backward compatibility, it is necessary to expose these capabilities in both server_capabilities and ext_server_capabilities. This dual exposure ensures that extensions can access all advertised features safely, regardless of the client architecture.

MySQL has also introduced a forward-looking mechanism for capability expansion via the CLIENT_CAPABILITY_EXTENSION flag. This flag reserves space for extending the traditional 32-bit capability field to 64 bits. As both MySQL and MariaDB move toward extended capability sets, it becomes important to clearly separate vendor-specific extensions.

To avoid ambiguity and potential conflicts between MySQL and MariaDB capability flags in the extended range, PHP should introduce distinct namespaces for these flags. For example, MariaDB-specific extended capabilities could be exposed using a prefix such as MYSQLI_MARIADB_*, ensuring that they remain clearly distinguishable from MySQL-defined flags and any future extensions

This capability-based detection allows PHP extensions to:

Example:

<?php
 
$link = new mysqli('127.0.0.1', 'my_user', 'my_password', 'test');
 
/* Check if we are connected to MariaDB or MySQL */
 
if ($link->server_capabilities & MYSQLI_CLIENT_MYSQL) {
	printf("Connected to MySQL server\n");
} else {
	printf("Connected to Mariadb server\n");
 
	printf("Bulk operations are ");
	if ($link->extended_server_capabilities & MYSQLI_MARIADB_CLIENT_STMT_BULK_OPERATIONS) {
		printf("supported\n");
	} else {
		printf("not supported\n");
	}
}
 
$link->close();
?>

Bulk Execution

MariaDB provides mechanisms to improve the performance of repeated or multi-row statements through bulk execution. The most common scenario is INSERT statements. In many cases, a standard single-row INSERT or REPLACE can be rewritten using the multi-value syntax:

 INSERT INTO table (col1, col2) VALUES (?, ?), (?, ?), ..., (?, ?) 

This approach can significantly reduce network round-trips and server parsing overhead. However, it has some limitations:

The MySQL protocol already anticipated support for bulk operations when prepared statements were introduced in MySQL 4.1. Prepared statement headers include a 4-byte iteration count, intended to allow repeated execution of a statement. This field, however, was never used by MySQL.

MariaDB addressed this limitation in version 10.2 by introducing a new command, COM_STMT_BULK_EXECUTE, which implements an enhanced protocol for executing bulk operations. This protocol provides several advantages:

From a PHP perspective, exposing bulk execution through mysqli or PDO would allow applications to take advantage of these performance gains without requiring changes to existing SQL logic, other than optionally preparing statements for bulk execution.

Example:

<?php
 
use Mysqlnd\Indicator;
 
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$link = new mysqli('127.0.0.1', 'my_user', 'my_pass', 'test');
 
$link->query("DROP TABLE IF EXISTS t1");
$link->query("CREATE TABLE t1 (id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50), score double default 20)");
 
$rows = [
	["Jennifer", 20.34],
	["Marcus", 21.9],
	["Rasmus", 22.1],
	["Derick", 20.9],
	["Zak", 20.91]
];
 
$stmt = $link->prepare("INSERT INTO t1 (name, score) VALUES (?, ?) RETURNING id");
 
$stmt->executemany($rows);
$result= $stmt->get_result();
 
printf("%d rows inserted with the following id's:\n", $result->num_rows);
 
if ($rows = $result->fetch_all()) {
	printf("%s ", implode(", ", array_column($rows, 0)));
}
printf("\n");
$stmt->close();
 
$rows = [
	[20.39, 1],
	[24.02, 2],
	[Indicator::Default, 3]
];
 
$stmt= $link->prepare("UPDATE t1 SET score=? WHERE id=?");
$stmt->executemany($rows);
 
printf("%d rows updated\n", $stmt->affected_rows);
 
$link->query("DROP TABLE t1");
 
$link->close();
?>

Streaming Support: A Secure Alternative to LOAD DATA LOCAL INFILE

In many production environments, the LOAD DATA LOCAL INFILE command is disabled (local_infile=0) to mitigate security risks, such as unauthorized local file reads by a compromised client. While this secures the server, it often leaves developers with the slow alternative of executing thousands of individual INSERT statements.

executemany() provides a high-performance, secure alternative that operates over the standard Prepared Statement protocol. By accepting an iterable, it allows for “Streaming” data directly into the database.

Key Advantages:

The following snippet demonstrates how a Generator acts as a programmable filter, replacing the need for row-level “Ignore” indicators:

<?php
function csv_streamer(string $path): Generator {
    if (!$handle = fopen($path, 'r')) return;
 
    /* Skip first row */
    fgetcsv($handle, 0, ",", "\"", "\\");
 
    while (($row = fgetcsv($handle, 0, ",", "\"", "\\")) !== false) {
        /* Only yield 'ACTIVE' rows to the database */
        if (isset($row[2]) && $row[2] === 'ACTIVE') {
            /* Yielding data to executemany() without loading the whole file */
            yield [(int)$row[0], $row[1]];
        }
    }
    fclose($handle);
}
 
$stmt = $mysqli->prepare("INSERT INTO users (id, name) VALUES (?, ?)");
 
/* The driver 'drains' the generator, maintaining O(1) memory usage in PHP */
$stmt->executemany(csv_streamer('large_data_export.csv'));
?>

Prepared Statement Metadata Caching

When a prepared statement is created, the server normally returns statement metadata describing result columns. This metadata includes:

For applications that repeatedly execute identical SQL statements—common in request-based execution models—this metadata is typically transmitted and parsed repeatedly, even though it does not change.

MariaDB supports prepared statement metadata caching, allowing the client to indicate that it is capable of caching metadata locally. Once this capability is negotiated:

This optimization reduces:

Importantly, this does not change the logical protocol flow of prepared statements. Instead, it optimizes the existing flow by eliminating redundant metadata transfer when both client and server support caching. This improves performance transparently, without requiring changes to application code or SQL syntax.

Progress Indication

Long-running SQL statements such as large ALTER TABLE, CREATE INDEX or complex UPDATE operations can take a significant amount of time to complete. Traditionally, clients have no insight into the progress of such operations and can only wait for completion or timeout.

MariaDB provides server-side progress reporting, allowing the server to send progress information to the client while a statement is executing. This information includes:

Progress information is sent asynchronously during statement execution and does not alter the semantics of the SQL statement itself.

Key characteristics:

From a PHP perspective, exposing this feature allows applications to:

Example pseudo-code:

<?php
function callback_progress($current, $max) { 
  printf("Progress: %d / %d\n", $current, $max);
}
 
$mysqli->options(MYSQLI_OPT_PROGRESS_CALLBACK, 'callback_progress');
$mysqli->query("ALTER TABLE big_table ADD INDEX idx_col (col)");
?>

Authentication Plugins

MariaDB supports server-side authentication plugins that extend or replace the default MySQL authentication mechanisms. The most notable example is the parsec plugin, which became the default in recent MariaDB versions.

Currently, mysqlnd provides only limited support for MariaDB-specific authentication plugins. As a result, connections to MariaDB servers using these plugins can fail, or require fallback to less secure authentication methods, unless external mysqlnd authentication plugins are installed (for example, mysqlnd_parsec or mysqlnd_ed25519 from Packagist).

This feature is invisible to application code:

By supporting MariaDB authentication plugins at the protocol level, PHP extensions can safely and efficiently connect to current and future MariaDB servers without requiring separate extensions or application changes.

Extended Metadata

MariaDB extends standard MySQL metadata with additional type information to provide more precise data handling. This extended metadata allows clients to differentiate types that MySQL treats identically, improving type awareness, validation, and performance in PHP applications.

Key MariaDB extensions include:

* ext_type — indicates the server-side extended type for a column or parameter. * ext_format — indicates the data format used for transmission, which may differ from the standard MySQL format. This is particularly relevant for binary or structured types such as geometry or network types.

Type Format
GEOMETRY POINT, LINESTRING, POLYGON, ...
STRING JSON, UUID, INET, ...

Together, ext_type and ext_format allow clients to:

From a PHP perspective, exposing this extended metadata through mysqli allows applications to:

PDO Considerations (Out of Scope)

PDO_MySQL currently does not support array binding or bulk execution semantics required to expose MariaDB-specific bulk operations in a clean and consistent way.

This RFC focuses on enhancing mysqlnd and mysqli first. Future RFCs may extend PDO_MySQL once the underlying mysqlnd capabilities are available and an appropriate PDO API design is agreed upon.

Implementation / Proof-of-Concept

A proof-of-concept implementation is available at: php-src 8.6-mariadb POC

The implementation modifies:

mysqlnd

mysqli

The patch is intended for PHP 8.6 and requires MariaDB Server ≥ 10.2 for full functionality.

Fallback behavior for MySQL Server remains unchanged and continues to use repeated COM_STMT_EXECUTE.

Performance Analysis: executemany() vs. sequential execute()

It is impossible to say that executemany() is “n-times” faster than a sequential execute() loop as a universal rule. The performance delta is highly dependent on two primary factors: hardware processing power and network latency.

For this reason, two distinct scenarios were chosen for this analysis:

Comparative Measurements

Operation Type Environment executemany() (rows/sec) execute() Loop (rows/sec) Improvement Factor
INSERT Remote (Ethernet) 54,848.65 8,689.18 6.3x
UPDATE Remote (Ethernet) 170,287.65 5,723.03 29.7x
DELETE Remote (Ethernet) 187,677.20 5,859.34 32.0x
INSERT Localhost 56,523.73 35,603.94 1.6x
UPDATE Localhost 178,297.41 70,286.37 2.5x
DELETE Localhost 236,210.99 76,604.79 3.1x

---

Analysis of Performance Factors

The measurements reveal how these two variables determine the “efficiency gap.”

1. Network Latency (The Round-Trip Penalty)

The most dramatic performance gains are observed in the remote Ethernet environment. While executemany() performance remains relatively stable across both environments, the sequential loop suffers a massive penalty over the network.

In a loop, the application is IO-bound, waiting for the network acknowledgement of each individual row before sending the next. executemany() mitigates this by wrapping multiple operations into fewer network packets, effectively amortizing the Round-Trip Time (RTT) cost.

2. Hardware and Protocol Overhead

On the Raspberry Pi 4, the CPU overhead of preparing and sending individual command packets is significantly higher than on a standard desktop.

Conclusion

For remote clients or resource-constrained hardware like the Raspberry Pi, executemany() is mandatory for high-performance applications, offering up to a 32x speedup. The data suggests that as network latency increases or hardware power decreases, the necessity of batching becomes exponentially more critical.

Measurements taken using Raspberry Pi 4 (Client) via Ethernet to 192.168.10.1 (Server) and local loopback (127.0.0.1).

This section evaluates the throughput efficiency of batch operations (executemany()) compared to iterative single-row operations (execute() in a loop). The data confirms that batching is a critical optimization, especially when network latency or hardware constraints are present.

Measurements taken using Raspberry Pi 4 (Client) via Ethernet to 192.168.10.1 (Server) and local loopback (127.0.0.1).

Visibility and Impacts

MariaDB-specific features can be categorized based on whether they require changes to the PHP API or are handled entirely internally by mysqlnd or the PHP extensions.

Visible Features

These features require new methods, functions, or options in PHP to expose MariaDB-specific functionality to developers:

Feature PHP Extension Description Required API Change
Server capabilities mysqli Additional connection properties New: $link->server_capabiities and $link->ext_server_capabilities
Bulk Execution mysqli Execute multiple sets of parameters efficiently in one round-trip $stmt->executemany($rows)
Progress Indication mysqli Report progress of long-running operations Additional parameter MYSQLI_OPT_PROGRESS_CALLBACK for mysqli_options

Impact: Applications can call these new methods directly to leverage MariaDB-specific performance or monitoring features. Backward compatibility with MySQL is maintained because these methods can be no-ops when connected to a MySQL server.

Invisible Features

These features are internal optimizations that improve performance or protocol behavior without requiring changes to application code:

Feature PHP Extension Description Notes
Prepared Statement Metadata Caching mysqlnd Avoids resending metadata for repeated prepares Transparent to application code
Authentication Plugins mysqlnd Supports MariaDB authentication plugins (e.g., parsec) Applications use standard connection functions
Extended Metadata / Type Granularity mysqli Provides ext_type and ext_format for geometry, INET, UUID Transparent to application code

Impacts

Performance Improvements: Existing PHP functions that prepare and execute statements, such as mysql_execute_query() (introduced in PHP 8.2), could be updated to use execute_direct when connected to a MariaDB server. This would allow one-time statements to bypass the extra round-trip and metadata fetch, providing immediate performance gains.

Backward Compatibility: All invisible features are fully backward-compatible with MySQL. Visible features are MariaDB-specific but can be safely ignored or no-oped when connected to a MySQL server.

Backward Incompatible Changes

None

RFC Impact

Open Issues

Future Scope

Implementation

References

Rejected Features

Changelog

If there are major changes to the initial proposal, please include a short summary with a date or a link to the mailing list announcement here, as not everyone has access to the wikis' version history.