Skip to main content
MediaWiki provides a database abstraction layer that handles connection management, query building, replication lag, and cross-database compatibility. All database access goes through this layer rather than calling PHP’s database extensions directly.

Getting a connection

The primary entry point is IConnectionProvider, retrieved from the service container:
use MediaWiki\MediaWikiServices;

$dbProvider = MediaWikiServices::getInstance()->getConnectionProvider();
The IConnectionProvider interface exposes two methods that reflect the read/write split:
// For read queries — uses a replica database when available
$dbr = $dbProvider->getReplicaDatabase();

// For write queries — always uses the primary database
$dbw = $dbProvider->getPrimaryDatabase();
The convention $dbr (replica/read) and $dbw (write/primary) is used throughout MediaWiki’s codebase. Always use the correct handle for the operation you are performing.

The read/write split

Large MediaWiki installations like Wikimedia run a primary MySQL server with many replica servers. Replicas handle all read traffic; the primary handles writes. The IConnectionProvider interface encodes this pattern directly.
Never write to a replica database handle ($dbr). If you do, your write will succeed locally but fail when replicated to the replica due to unique key conflicts or other constraint violations. Replication will stop and may require hours of manual repair.

getReplicaDatabase()

Returns a connection to a replica (read-only). Use for all SELECT queries. Automatically load-balances across available replicas.

getPrimaryDatabase()

Returns a connection to the primary (read/write). Use for INSERT, UPDATE, DELETE, and any read that must see the latest committed data.

A complete read/write example

use MediaWiki\MediaWikiServices;

$dbProvider = MediaWikiServices::getInstance()->getConnectionProvider();

// Read from a replica
$dbr = $dbProvider->getReplicaDatabase();
$res = $dbr->newSelectQueryBuilder()
    ->select( [ 'page_id', 'page_title' ] )
    ->from( 'page' )
    ->where( [ 'page_namespace' => NS_MAIN ] )
    ->limit( 10 )
    ->fetchResultSet();

foreach ( $res as $row ) {
    // process $row->page_id, $row->page_title
}

// Write to the primary
$dbw = $dbProvider->getPrimaryDatabase();
$dbw->newInsertQueryBuilder()
    ->insertInto( 'my_extension_table' )
    ->row( [ 'col_page_id' => 42, 'col_data' => 'value' ] )
    ->caller( __METHOD__ )
    ->execute();

Supported database backends

MediaWiki is written primarily for MySQL/MariaDB, which is the canonical database and has the most optimized schema. The following backends are also supported:

MySQL / MariaDB

The primary supported backend. Queries and schema are optimized for it. Required for production Wikimedia deployments.

PostgreSQL

Supported to varying degrees. Additional configuration and known issues are documented in docs/databases/.

SQLite

Supported for small or development installations. Not suitable for high-traffic production use.
The abstract schema system (see Database Schema) allows a single table definition to generate correct DDL for all three backends.

Transaction handling

By default, MediaWiki opens a transaction at the first query in a request and commits it before sending the response. This means locks are held from query execution until the end of the request. To minimize lock contention, do as much computation as possible before issuing write queries. When you need tighter control, you can open an explicit transaction around a small group of writes:
$dbw = $dbProvider->getPrimaryDatabase();
$dbw->begin( __METHOD__ );

// A series of related write queries
$dbw->newInsertQueryBuilder()
    ->insertInto( 'table_a' )
    ->row( [ /* ... */ ] )
    ->caller( __METHOD__ )
    ->execute();

$dbw->newUpdateQueryBuilder()
    ->update( 'table_b' )
    ->set( [ 'col' => 'value' ] )
    ->where( [ 'id' => 1 ] )
    ->caller( __METHOD__ )
    ->execute();

$dbw->commit( __METHOD__ );
Avoid locking reads (SELECT ... FOR UPDATE). They are poorly implemented in InnoDB and cause frequent deadlocks. Instead, combine existence checks into your write queries using conditional WHERE clauses, or rely on unique indexes with INSERT IGNORE and check the affected row count.

Replication lag

Replicas apply writes from the primary serially, so a long write query causes replicas to lag behind. MediaWiki’s load balancer avoids sending reads to a replica lagged by more than a few seconds (LoadBalancer::MAX_LAG_DEFAULT = 6 seconds, configurable via max lag in $wgLBFactoryConf). When a replica is too far behind, MediaWiki enters lagged replica mode (ILBFactory::laggedReplicaUsed), which automatically shortens object cache and CDN cache expiry so stale data converges quickly. To write lag-friendly code:
1

Split large writes

Break bulk writes into one-row-at-a-time loops. Multi-row INSERT ... SELECT queries are the worst offenders and should be avoided. Do the SELECT first, then INSERT row by row.
2

Read from replicas

For the vast majority of reads, replica data is acceptable. Sending all queries to the primary to avoid lag is not a scalable solution.
3

Handle genuinely stale reads

If you must ensure up-to-date data: (1) query the primary for a sequence number or timestamp, (2) run the full query on a replica and compare, (3) fall back to the primary only if the replica is behind. Use this pattern sparingly.

Query groups

Query groups direct replica reads to a preferred set of database hosts. This reduces cache misses by concentrating similar queries on the same host, and isolates expensive queries from impacting general traffic.
$dbProvider = MediaWikiServices::getInstance()->getConnectionProvider();

// Direct this query to the 'vslow' replica group
$dbr = $dbProvider->getReplicaDatabase( false, 'vslow' );
Supported query groups:
GroupPurpose
apiQueries specific to api.php requests, set via ApiBase::getDB()
dumpDump and export maintenance scripts; affects all queries in the process
vslowExpected long-running queries (> 1 second on large wikis); use in jobs, maintenance scripts, or behind $wgMiserMode
Using query groups does not guarantee a specific host will be used — it expresses a preference. The load balancer falls back to any available replica if the preferred group has no suitable host.

Cross-wiki database access

Some multi-wiki installations share tables (for example, a central user table) across wikis using $wgSharedDB. MediaWiki’s LBFactory provides cross-wiki connection management through getDBLoadBalancerFactory(), but for most extension development you should use IConnectionProvider directly and let the framework handle routing.
// Only use LBFactory when you need explicit cross-wiki access.
// For normal extension code, prefer getConnectionProvider().
$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
$lb = $lbFactory->getMainLB( $wikiId );
$dbr = $lb->getConnection( DB_REPLICA, [], $wikiId );

GROUP BY compatibility

MySQL allows GROUP BY without listing all non-aggregate SELECT columns. PostgreSQL and SQLite are stricter and require every non-aggregate column in SELECT to also appear in GROUP BY. For this reason, avoid SELECT * in any query that uses GROUP BY.

Build docs developers (and LLMs) love