Usare database multipli in WordPress

Per impostazione predefinita WordPress utilizza un solo database (tipicamente MySQL/MariaDB) definito in wp-config.php tramite costanti come DB_NAME, DB_USER, DB_PASSWORD e DB_HOST. In molti progetti, però, si presenta l’esigenza di usare più di un database: separare dati applicativi dai contenuti, integrare un gestionale esterno, archiviare log/telemetria, distribuire il carico (repliche in lettura), o “shardare” tabelle molto grandi.

Questo articolo spiega approcci, pro e contro e implementazioni per lavorare con più database in WordPress, con esempi pronti da adattare.

Quando ha senso usare più database

  • Integrazione con un database esterno (ERP/CRM, catalogo prodotti, magazzino, anagrafiche).
  • Separazione dei dati: ad esempio log applicativi o eventi in un DB dedicato.
  • Prestazioni e scalabilità: repliche in lettura, separazione di tabelle ad alta intensità I/O.
  • Manutenzione e governance: policy diverse di backup/retention per set di dati differenti.
  • Conformità: isolare dati sensibili in un’istanza/cluster separato con controlli più rigidi.

Panoramica degli approcci

  1. Connessione aggiuntiva “ad hoc” per leggere/scrivere su un database esterno (senza toccare il DB principale di WordPress).
  2. Routing del DB di WordPress su più database (repliche, sharding, separazione per tabelle) tramite un db drop-in (ad es. HyperDB / LudicrousDB) o una personalizzazione avanzata di wpdb.
  3. Servizi esterni (API, data warehouse, ecc.) che sostituiscono l’accesso diretto al secondo DB: non è “più database in WP” in senso stretto, ma spesso è più robusto e sicuro.

Prerequisiti e note importanti

  • Verifica che l’hosting consenta connessioni a DB remoti (firewall, allowlist IP, porte, TLS).
  • Evita di salvare credenziali in chiaro nel repository: usa variabili d’ambiente o secret manager.
  • Proteggi le query: usa preparazione parametri, escaping, permessi minimi per l’utente DB.
  • Considera la coerenza: due database significano due transazioni separate (niente ACID cross-DB senza sistemi dedicati).

Approccio 1: collegarsi a un database esterno con una seconda istanza di wpdb

Il modo più semplice per usare un secondo database è creare una nuova istanza della classe wpdb. Così WordPress continua a usare il proprio DB, mentre tu esegui query sul DB esterno quando serve.

1.1 Definire le credenziali in modo sicuro

In wp-config.php puoi leggere le credenziali da variabili d’ambiente (o da un file non versionato). Esempio:

// wp-config.php

define('EXTDB_NAME', getenv('EXTDB_NAME') ?: '');
define('EXTDB_USER', getenv('EXTDB_USER') ?: '');
define('EXTDB_PASSWORD', getenv('EXTDB_PASSWORD') ?: '');
define('EXTDB_HOST', getenv('EXTDB_HOST') ?: 'localhost');

// Facoltativo: charset/collation del DB esterno
define('EXTDB_CHARSET', 'utf8mb4');
define('EXTDB_COLLATE', 'utf8mb4_unicode_ci');

1.2 Creare un client DB esterno (mu-plugin o plugin)

È consigliabile incapsulare la connessione in una funzione o classe, idealmente in un MU-Plugin (wp-content/mu-plugins/) così viene caricato sempre e prima dei plugin standard.

<?php
// file: wp-content/mu-plugins/external-db.php

if (!defined('ABSPATH')) { exit; }

function my_extdb() : wpdb {
  static $extdb = null;

  if ($extdb instanceof wpdb) {
    return $extdb;
  }

  // Crea una nuova istanza: NON influenza $wpdb globale
  $extdb = new wpdb(EXTDB_USER, EXTDB_PASSWORD, EXTDB_NAME, EXTDB_HOST);

  // Imposta charset e collate, se necessari
  if (defined('EXTDB_CHARSET') && EXTDB_CHARSET) {
    $extdb->set_charset($extdb->dbh, EXTDB_CHARSET, defined('EXTDB_COLLATE') ? EXTDB_COLLATE : '');
  }

  // In produzione evita di mostrare errori SQL
  $extdb->hide_errors();

  return $extdb;
}

1.3 Eseguire query in sicurezza

Usa prepare() per parametri, ed evita di interpolare valori direttamente nella stringa SQL.

$extdb = my_extdb();

$user_id = 123;
$row = $extdb->get_row(
  $extdb->prepare(
    "SELECT id, email, status FROM external_users WHERE wp_user_id = %d",
    $user_id
  ),
  ARRAY_A
);

if ($row) {
  // ...
}

1.4 Scritture, error handling e debug

Quando inserisci o aggiorni, controlla sempre il risultato e gli errori. Per troubleshooting puoi temporaneamente abilitare show_errors() su ambiente di sviluppo.

$extdb = my_extdb();

$inserted = $extdb->insert(
  'external_events',
  [
    'wp_user_id' => 123,
    'event_type' => 'login',
    'created_at' => current_time('mysql', true),
  ],
  ['%d', '%s', '%s']
);

if ($inserted === false) {
  error_log('EXTDB insert error: ' . $extdb->last_error);
}

1.5 Performance: caching e riduzione delle query

Se il DB esterno viene consultato spesso, riduci la pressione con cache applicativa (transient o object cache persistente). Esempio con transients:

$cache_key = 'ext_user_' . $user_id;
$data = get_transient($cache_key);

if ($data === false) {
  $extdb = my_extdb();
  $data = $extdb->get_row(
    $extdb->prepare("SELECT * FROM external_users WHERE wp_user_id = %d", $user_id),
    ARRAY_A
  );

  // Cache per 5 minuti
  set_transient($cache_key, $data, 5 * MINUTE_IN_SECONDS);
}

Approccio 2: usare più database per le tabelle di WordPress (routing/sharding/repliche)

Questo approccio è più complesso: invece di aggiungere un DB “esterno”, si modifica come WordPress sceglie la connessione per le sue query interne. È utile quando vuoi:

  • repliche in lettura (read replicas) per alleggerire il primario;
  • separare tabelle (ad es. wp_options su un DB, wp_posts su un altro);
  • sharding per multisite o per tabelle enormi.

2.1 Il concetto di “db drop-in”

WordPress supporta un file speciale chiamato db.php in wp-content/, detto drop-in. Se presente, WordPress lo carica per gestire la connessione al database. Soluzioni come HyperDB o LudicrousDB sfruttano questo meccanismo per instradare le query verso diversi server/database in base a regole.

Vantaggi: routing centralizzato, repliche e failover.

Svantaggi: maggiore complessità operativa, test accurati necessari, attenzione a coerenza e latenza tra primario e replica.

2.2 Schema tipico con primario + replica in lettura

Una strategia comune è inviare le query SELECT a una replica e le query INSERT/UPDATE/DELETE al primario. Il drop-in decide in base al tipo di query e (spesso) al contesto: durante login, aggiornamenti di opzioni, scrittura di sessioni e carrelli, è preferibile forzare il primario.

Molte implementazioni forniscono una configurazione in PHP; concettualmente, lo schema è questo:

// Esempio concettuale (non è una configurazione universale)
// Primario (scritture) e Replica (letture)

$databases = [
  'primary' => [
    'host' => '10.0.0.10',
    'name' => 'wp_main',
    'user' => 'wp_user',
    'pass' => '***',
    'write' => true,
    'read'  => true,
  ],
  'replica_1' => [
    'host' => '10.0.0.11',
    'name' => 'wp_main',
    'user' => 'wp_ro',
    'pass' => '***',
    'write' => false,
    'read'  => true,
  ],
];

// Regola: SELECT -> replica, altro -> primario
function pick_db_for_query(string $sql) : string {
  return (preg_match('/^\s*SELECT\b/i', $sql)) ? 'replica_1' : 'primary';
}

In pratica, dovrai seguire la documentazione della soluzione scelta (HyperDB/LudicrousDB o alternativa) perché formati e funzionalità variano. Prima di andare in produzione, testa:

  • latenza di replica (eventuale lettura di dati “vecchi”);
  • coerenza durante checkout, login, aggiornamenti profilo;
  • fallimento di un nodo (failover) e recupero;
  • compatibilità con plugin critici (e-commerce, membership, cache, ecc.).

2.3 Separare tabelle su database diversi

Separare tabelle può avere senso quando alcune sono molto più “calde” di altre. Esempi:

  • wp_options (molte letture) su un database ottimizzato per letture;
  • wp_posts/wp_postmeta su un cluster dedicato;
  • tabelle di log su un DB a parte per non impattare il DB principale.

Il routing “per tabella” richiede regole robuste: WordPress (e molti plugin) eseguono query con join tra più tabelle. Se una query unisce tabelle su database diversi, non puoi fare un join diretto senza usare federazione o soluzioni specifiche, e potresti dover ripensare l’architettura.

Approccio 3: tabelle personalizzate in un secondo database

Se stai creando funzionalità custom (ad es. analytics, eventi, import massivi), spesso il compromesso migliore è:

  • lasciare WordPress sul suo DB;
  • creare tabelle custom in un DB separato;
  • accedere con una seconda istanza wpdb (Approccio 1).

3.1 Creazione tabelle e migrazioni

Per il DB di WordPress esiste dbDelta(). Per un DB esterno puoi gestire migrazioni con SQL versionato (o strumenti come Flyway/Liquibase a livello infrastrutturale). Un esempio minimale di “migrazione” gestita in plugin:

function my_extdb_migrate() {
  $extdb = my_extdb();

  $sql = "
    CREATE TABLE IF NOT EXISTS external_events (
      id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
      wp_user_id BIGINT UNSIGNED NOT NULL,
      event_type VARCHAR(64) NOT NULL,
      created_at DATETIME NOT NULL,
      PRIMARY KEY (id),
      KEY wp_user_id (wp_user_id),
      KEY event_type (event_type),
      KEY created_at (created_at)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
  ";

  $ok = $extdb->query($sql);
  if ($ok === false) {
    error_log('EXTDB migrate error: ' . $extdb->last_error);
  }
}

3.2 Accesso in lettura intensiva: indicizzazione e query plan

Quando separi dati su un DB esterno, ottimizzi più facilmente indici e schemi per il tuo caso d’uso. Buone pratiche:

  • indice sulle colonne usate in filtri e ordinamenti;
  • evita colonne “blob” se non necessarie;
  • usa EXPLAIN per validare i piani di esecuzione;
  • paginazione coerente (chiavi stabili, evitare offset enormi).
EXPLAIN
SELECT wp_user_id, event_type, created_at
FROM external_events
WHERE wp_user_id = 123
ORDER BY created_at DESC
LIMIT 50;

Gestione di transazioni e consistenza tra due database

Se un’operazione deve scrivere su due database, devi scegliere una strategia di consistenza. In assenza di un coordinatore transazionale (2PC) è comune usare:

  • Consistenza eventuale: scrivi su WP, metti un evento in coda e aggiorni il DB esterno in background.
  • Outbox pattern: scrivi su WP e registri un record “outbox” nel DB principale; un worker legge e propaga verso il DB esterno, con retry.
  • Compensazione: se la seconda scrittura fallisce, registri un’azione di rollback logico (non sempre possibile).

Per molti siti, la strada più solida è evitare doppie scritture sincrone in request/response, e spostare l’integrazione su job asincroni (cron, queue, worker).

Sicurezza

  • Principio del minimo privilegio: crea utenti DB separati (read-only dove possibile).
  • TLS se il DB è remoto; preferisci reti private/VPN.
  • Sanificazione e query preparate: $wpdb->prepare().
  • Segreti: non hardcodare credenziali; limita accessi e ruota password.
  • Audit: logga errori e tempi di risposta, ma senza includere password o dati sensibili.

Testing e deployment

  • Ambienti separati (dev/stage/prod) con DB diversi e credenziali diverse.
  • Test di integrazione: simula indisponibilità del DB esterno e verifica i fallback.
  • Timeout ragionevoli e retry controllati (evitare “thundering herd”).
  • Backup: definisci piani distinti per ciascun DB (RPO/RTO diversi).

Checklist rapida

  1. Definisci perché ti serve un secondo DB (integrazione? performance? isolamento?).
  2. Se ti serve solo un DB esterno: usa una seconda istanza di wpdb (Approccio 1).
  3. Se vuoi routing delle query WP: valuta un db drop-in e prepara test accurati (Approccio 2).
  4. Gestisci caching e indici per ridurre query lente.
  5. Tratta consistenza e doppie scritture come un problema di architettura (queue/outbox).
  6. Metti in sicurezza credenziali, rete, permessi e logging.

Appendice: snippet utili

Verificare connessione al DB esterno

$extdb = my_extdb();

if (!$extdb->dbh) {
  error_log('EXTDB: handle non valido');
} else {
  $ok = $extdb->query('SELECT 1');
  if ($ok === false) {
    error_log('EXTDB: test query fallita: ' . $extdb->last_error);
  }
}

Comando per testare accesso da CLI (utile in debug infrastrutturale)

mysql -h EXTDB_HOST -u EXTDB_USER -p EXTDB_NAME -e "SELECT NOW();"

Con questi approcci puoi scegliere la soluzione più adatta: dalla semplice integrazione con un database esterno fino a strategie avanzate di scalabilità con repliche e routing. Inizia sempre dal caso più semplice che soddisfa i requisiti e introduci complessità solo quando è davvero necessaria.

Torna su