Si vous avez déjà vu “MySQL server has gone away” au milieu d’un import WooCommerce, d’un build Elementor, ou d’un cron qui tourne depuis 20 minutes, vous avez touché un problème qui n’est presque jamais “juste WordPress”. La cause réelle est un timeout, une limite serveur, ou une connexion DB tuée pendant une opération trop longue.


Le problème

Le message d’erreur apparaît généralement sous une de ces formes (logs PHP, logs WordPress, Query Monitor, ou réponse JSON REST) :

WordPress database error MySQL server has gone away for query SELECT ... made by ...
PHP Fatal error:  Uncaught mysqli_sql_exception: MySQL server has gone away
Error 2006: MySQL server has gone away
Error 2013: Lost connection to MySQL server during query

Où vous le voyez :

  • Front-end : erreur 500, page blanche, bloc Gutenberg qui ne charge pas, “La réponse n’est pas une réponse JSON valide”.
  • wp-admin : sauvegarde d’options qui échoue, écran blanc lors d’un update, Media Library qui ne se charge plus.
  • REST API / AJAX : appels qui time-out, réponses tronquées, erreurs 502/504 via reverse proxy.
  • WP-Cron : tâches planifiées qui s’interrompent, actions WooCommerce “scheduled action” bloquées.

Circonstances typiques :

  • Après migration d’hébergeur (paramètres MySQL plus stricts).
  • Après activation d’un plugin qui fait des traitements longs (cache, SEO, import, analytics).
  • Lors d’une hausse de trafic (pics + pool PHP-FPM saturé).
  • Sur des sites “builder-heavy” (Divi 5, Elementor, Avada) avec beaucoup d’options sérialisées et de requêtes sur wp_options.

À qui s’adresse ce guide : blogueurs avancés, développeurs, équipes ops. À la fin, vous saurez identifier la limite exacte (MySQL, PHP-FPM, proxy, réseau), corriger le code qui garde une connexion ouverte trop longtemps, et ajuster la configuration serveur sans “augmenter tout au hasard”. WordPress visé : 6.9.4, PHP 8.1+.


Résumé rapide

  • 2006 “server has gone away” = MySQL a fermé la connexion (timeout, paquet trop gros, kill, redémarrage).
  • 2013 “lost connection during query” = la connexion tombe pendant une requête (requête lente, réseau, proxy, saturation).
  • Commencez par corréler l’heure exacte avec les logs MySQL + PHP-FPM + Nginx/Apache.
  • Côté WordPress : évitez les traitements “monolithiques” (imports, rebuild cache) en un seul hit. Chunking + reprise = moins de connexions longues.
  • Côté serveur : les suspects n°1 sont wait_timeout, max_allowed_packet, PHP-FPM request_terminate_timeout, et les timeouts proxy.
  • Ne “fixez” pas en augmentant seulement WP_MEMORY_LIMIT : ça masque parfois un problème de requêtes et aggrave la charge.

Les symptômes

Vous n’aurez pas toujours le message exact affiché. Souvent, c’est une cascade.

  • Erreur 500 sporadique sur des pages lourdes (builder + WooCommerce + beaucoup de hooks).
  • Admin qui se déconnecte (nonce expiré car la requête a pris trop de temps) puis erreur DB.
  • REST API : cURL error 28: Operation timed out côté client et MySQL server has gone away côté serveur.
  • AJAX (Elementor editor, Divi Visual Builder) : spinner infini, puis “The response is not a valid JSON response”.
  • WP-Cron : tâches qui se relancent en boucle (doublons), ou qui restent “in-progress”.
  • Query Monitor : requêtes très longues, ou “MySQL has gone away” sur une page précise.
  • Cache : le problème disparaît quand le cache page est chaud, revient sur cache froid.

Symptômes secondaires que j’ai souvent vus sur des sites Avada/Divi/Elementor :

  • Un module qui lit/écrit massivement dans wp_options (autoloader trop chargé) et déclenche des requêtes lentes.
  • Des transients géants (payload sérialisé énorme) qui font exploser max_allowed_packet.
  • Un import qui fait des UPDATE par item sans index correct, puis dépasse les timeouts.

Pourquoi ça arrive

Explication simple

WordPress parle à MySQL/MariaDB via PHP. Si une requête prend trop longtemps, si la connexion reste inactive, si le serveur DB redémarre, ou si le serveur refuse un paquet trop gros, MySQL coupe. PHP tente ensuite de continuer, et vous voyez “gone away”.

Explication technique (ce qui se passe en coulisses)

Avec WordPress 6.9.4, la couche DB utilise toujours wpdb (mysqli si disponible). Une requête peut échouer pour :

  • Timeout côté MySQL : wait_timeout (connexions non interactives), interactive_timeout, ou kill par le serveur.
  • Paquet trop gros : max_allowed_packet (classique avec gros INSERT, options sérialisées, postmeta massif, ou requêtes préparées contenant de gros blobs).
  • Requête trop lente : la connexion est rompue pendant l’exécution (erreur 2013) ou le proxy coupe avant.
  • Ressources : OOM killer, saturation CPU/IO, pool PHP-FPM saturé, MySQL redémarre (crash/restart), ou limitation imposée par l’hébergeur.
  • Réseau : DB distante (RDS, Cloud SQL), NAT idle timeout, load balancer, TLS renegotiation, etc.

Causes probables (du plus fréquent au plus rare)

  1. Imports / traitements longs exécutés dans une requête HTTP unique (admin, REST, AJAX).
  2. max_allowed_packet trop bas (transients/options énormes, grosse requête multi-row).
  3. wait_timeout trop bas + connexions réutilisées longtemps (rare en PHP pur, plus fréquent via proxies DB/DBaaS).
  4. Proxy timeouts (Nginx proxy_read_timeout, Cloudflare, ALB) qui tuent la requête avant la fin, laissant des états incohérents.
  5. MySQL redémarre (OOM, crash, mise à jour, rotation logs, failover).
  6. Corruption / tables énormes + requêtes non indexées, surtout sur wp_postmeta et wp_options.

Variante piégeuse : vous corrigez MySQL, mais le problème était PHP-FPM qui tue le worker via request_terminate_timeout, donnant l’impression que “MySQL est parti”. Dans les logs, vous verrez plutôt un 502/504 et des workers terminés.


Prérequis avant de commencer

  • Sauvegarde complète (fichiers + base) et idéalement un clone de staging.
  • WordPress 6.9.4, PHP 8.1+ (si vous êtes en 8.0 ou moins, commencez par migrer : vous cumulez bugs et performances dégradées).
  • Accès aux logs : PHP-FPM, Nginx/Apache, MySQL/MariaDB, et si DB managée, logs provider.
  • Plugins de diagnostic :
  • Debug WordPress activable temporairement (sur staging) via wp-config.php :
<?php
// wp-config.php (staging uniquement)
// Attention : ne laissez pas WP_DEBUG_LOG activé sur un site très fréquenté sans rotation de logs.

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );

// Optionnel : log des requêtes (coûteux). À activer seulement pour une fenêtre courte.
// define( 'SAVEQUERIES', true );

Référence officielle : Debugging in WordPress.


Solution 1 : Réduire les connexions longues et les requêtes lourdes (côté WordPress)

Quand “gone away” arrive sur une action précise (import, génération de thumbnails, rebuild index), le correctif le plus fiable consiste à arrêter de tout faire en une seule requête. Vous découpez en lots, vous reprenez, et vous évitez les payloads énormes.

Cas réel : un plugin/snippet qui batch des INSERT géants

J’ai souvent vu ce pattern dans des imports “maison” : on construit une requête multi-row énorme, et MySQL refuse le paquet ou coupe la connexion.

AVANT (cassé) : requête trop grosse, pas de reprise

<?php
// functions.php ou plugin custom
// Problème : une seule requête massive peut dépasser max_allowed_packet
// et/ou monopoliser un worker PHP trop longtemps.

function bpcab_importer_meta_massif( array $rows ) {
	global $wpdb;

	$values = array();

	foreach ( $rows as $row ) {
		$post_id = (int) $row['post_id'];
		$key     = (string) $row['meta_key'];
		$value   = maybe_serialize( $row['meta_value'] );

		// Attention : concaténation naïve (et risquée si mal échappée)
		$values[] = $wpdb->prepare( "(%d, %s, %s)", $post_id, $key, $value );
	}

	$sql = "INSERT INTO {$wpdb->postmeta} (post_id, meta_key, meta_value) VALUES " . implode( ',', $values );
	$wpdb->query( $sql ); // Peut déclencher "MySQL server has gone away"
}

APRÈS (corrigé) : chunking + limites + reprise

<?php
// Plugin custom recommandé (mu-plugin si c'est infra), PHP 8.1+
// Objectif : limiter la taille des requêtes et le temps d'exécution.

function bpcab_importer_meta_en_lots( array $rows, int $chunk_size = 200 ) : array {
	global $wpdb;

	$chunks      = array_chunk( $rows, max( 1, $chunk_size ) );
	$inserted    = 0;
	$last_offset = 0;

	foreach ( $chunks as $i => $chunk ) {
		$values = array();

		foreach ( $chunk as $row ) {
			$post_id = (int) ( $row['post_id'] ?? 0 );
			$key     = (string) ( $row['meta_key'] ?? '' );

			// On refuse les payloads absurdes : c'est souvent là que max_allowed_packet explose.
			$value = isset( $row['meta_value'] ) ? maybe_serialize( $row['meta_value'] ) : '';
			if ( strlen( $value ) > 1024 * 512 ) { // 512 KB, à ajuster
				continue;
			}

			$values[] = $wpdb->prepare( "(%d, %s, %s)", $post_id, $key, $value );
		}

		if ( empty( $values ) ) {
			$last_offset = ( $i + 1 ) * $chunk_size;
			continue;
		}

		$sql = "INSERT INTO {$wpdb->postmeta} (post_id, meta_key, meta_value) VALUES " . implode( ',', $values );

		$result = $wpdb->query( $sql );

		if ( false === $result ) {
			// On remonte une erreur exploitable (logs WP_DEBUG_LOG)
			return array(
				'ok'          => false,
				'inserted'    => $inserted,
				'last_offset' => $last_offset,
				'db_error'    => $wpdb->last_error,
			);
		}

		$inserted   += (int) $result;
		$last_offset = ( $i + 1 ) * $chunk_size;
	}

	return array(
		'ok'          => true,
		'inserted'    => $inserted,
		'last_offset' => $last_offset,
	);
}

Pourquoi ça marche :

  • Vous réduisez la taille de chaque requête (moins de risque de dépasser max_allowed_packet).
  • Vous réduisez le temps passé sur un seul hit HTTP (moins de risque de timeout proxy/PHP-FPM).
  • Vous obtenez un offset de reprise exploitable (au lieu de recommencer à zéro).

Détail important : le “chunking” ne remplace pas l’indexation. Si chaque insert déclenche des verrous et une IO massive, vous devrez aussi regarder la santé de la table et les index.

Compatibilité Divi 5 / Elementor / Avada

Sur des sites builder, le problème se manifeste souvent pendant :

  • la génération CSS/asset (Divi 5, Avada) ;
  • la sauvegarde d’un layout lourd (Elementor) ;
  • la régénération de données (global styles, templates, etc.).

Le correctif côté WordPress reste le même : éviter les opérations “tout-en-un”. Si vous développez un module custom (Divi 5) ou un widget Elementor, isolez les traitements lourds dans une tâche asynchrone (voir Solution 2) au lieu de les faire pendant l’édition visuelle.


Solution 2 : Rendre WP-Cron et les tâches asynchrones plus robustes

Beaucoup de “gone away” arrivent sur des tâches planifiées qui font trop de choses : synchronisation, purge cache, import, recalcul de stats. Le symptôme est trompeur : on pense MySQL, mais la vraie cause est une exécution longue, interrompue, puis relancée.

AVANT (cassé) : cron monolithique, pas de verrou, pas de reprise

<?php
// Problème : la tâche peut durer plusieurs minutes.
// Si elle dépasse un timeout, elle peut être relancée et doubler la charge DB.

add_action( 'bpcab_cron_rebuild', function () {
	$posts = get_posts( array(
		'post_type'      => 'post',
		'posts_per_page' => -1,
		'fields'         => 'ids',
	) );

	foreach ( $posts as $post_id ) {
		// Simulation : recalcul lourd (meta, index, etc.)
		update_post_meta( $post_id, '_bpcab_index', wp_json_encode( array( 'ts' => time() ) ) );
	}
} );

APRÈS (corrigé) : lots + verrou + curseur persistant

<?php
/**
 * Exemple "robuste" : traitement par lots, avec verrou et reprise.
 * À mettre dans un plugin custom (idéalement mu-plugin).
 */

// Nom d'option pour stocker la progression.
const BPCAB_REBUILD_CURSOR_OPTION = 'bpcab_rebuild_cursor';
// Nom de verrou simple (transient).
const BPCAB_REBUILD_LOCK_TRANSIENT = 'bpcab_rebuild_lock';

add_action( 'bpcab_cron_rebuild', 'bpcab_rebuild_index_job' );

function bpcab_rebuild_index_job() : void {
	// Verrou 5 minutes : évite les exécutions concurrentes.
	if ( get_transient( BPCAB_REBUILD_LOCK_TRANSIENT ) ) {
		return;
	}
	set_transient( BPCAB_REBUILD_LOCK_TRANSIENT, 1, 5 * MINUTE_IN_SECONDS );

	$cursor = (int) get_option( BPCAB_REBUILD_CURSOR_OPTION, 0 );
	$limit  = 200;

	$query = new WP_Query( array(
		'post_type'              => 'post',
		'post_status'            => 'any',
		'posts_per_page'         => $limit,
		'orderby'                => 'ID',
		'order'                  => 'ASC',
		'fields'                 => 'ids',
		'no_found_rows'          => true,
		'update_post_meta_cache' => false,
		'update_post_term_cache' => false,
		'cache_results'          => false,
		// Curseur : on repart après le dernier ID traité
		'date_query'             => array(),
		'meta_query'             => array(),
		// Filtrage par ID via post__not_in est inefficace.
		// On préfère un WHERE ID > cursor via posts_where.
	) );

	add_filter( 'posts_where', function ( $where ) use ( $cursor ) {
		global $wpdb;
		if ( $cursor > 0 ) {
			$where .= $wpdb->prepare( " AND {$wpdb->posts}.ID > %d ", $cursor );
		}
		return $where;
	}, 10, 1 );

	$query->get_posts();

	remove_all_filters( 'posts_where' );

	$ids = $query->posts;

	if ( empty( $ids ) ) {
		// Terminé : on remet le curseur à zéro.
		delete_option( BPCAB_REBUILD_CURSOR_OPTION );
		delete_transient( BPCAB_REBUILD_LOCK_TRANSIENT );
		return;
	}

	foreach ( $ids as $post_id ) {
		update_post_meta(
			$post_id,
			'_bpcab_index',
			wp_json_encode( array( 'ts' => time(), 'id' => $post_id ), JSON_UNESCAPED_SLASHES )
		);
		$cursor = (int) $post_id;
	}

	update_option( BPCAB_REBUILD_CURSOR_OPTION, $cursor, false );

	// On relâche le verrou.
	delete_transient( BPCAB_REBUILD_LOCK_TRANSIENT );

	// On re-planifie vite (mais pas immédiat) pour limiter le temps par run.
	if ( ! wp_next_scheduled( 'bpcab_cron_rebuild' ) ) {
		wp_schedule_single_event( time() + 30, 'bpcab_cron_rebuild' );
	}
}

Pourquoi ça corrige :

  • Chaque exécution traite 200 posts max : vous restez sous les timeouts habituels.
  • Le verrou réduit les courses (deux crons qui se chevauchent et doublent la charge DB).
  • Le curseur évite de recommencer à zéro après un crash.

Edge cases à connaître :

  • posts_where : attention à ne pas laisser le filtre actif. Ici, je le retire immédiatement. J’ai déjà vu des sites casser l’admin entière car un filtre “temporaire” restait accroché.
  • Transients en DB : si votre site a un object cache persistant (Redis/Memcached), le verrou est plus fiable. Sans cache persistant, le transient est stocké en DB et ajoute un peu de charge, mais ça reste acceptable.
  • WP-Cron dépend du trafic. Sur un site à faible trafic, préférez un cron système qui appelle WP-CLI (voir Solution 3).

Référence : WP-Cron API.


Solution 3 : Régler MySQL/MariaDB et PHP-FPM pour éviter les timeouts

Quand le code est correct (pas de requêtes géantes, traitements chunkés) et que l’erreur persiste, vous êtes sur un problème de configuration. Le but n’est pas “tout augmenter”. Le but est d’aligner les timeouts entre : proxy ↔ PHP-FPM ↔ MySQL.

Étape 1 : confirmer la cause exacte côté MySQL

Sur le serveur DB (ou via votre provider), cherchez autour de l’heure :

  • redémarrages (crash recovery, failover) ;
  • erreurs Aborted connection ;
  • messages liés à max_allowed_packet ;
  • slow query log (requêtes > 1–2s).

Commandes utiles :

# Vérifier les variables clés
mysql -e "SHOW VARIABLES LIKE 'max_allowed_packet';"
mysql -e "SHOW VARIABLES LIKE 'wait_timeout';"
mysql -e "SHOW VARIABLES LIKE 'interactive_timeout';"

# Voir les connexions et requêtes en cours
mysql -e "SHOW FULL PROCESSLIST;"

Étape 2 : ajuster max_allowed_packet (cas très fréquent)

Si vous avez des imports, des options sérialisées lourdes, ou des transients énormes, max_allowed_packet trop bas provoque des coupures. Sur des serveurs modernes, 64M à 256M est courant selon le workload.

Exemple MySQL (mysqld) :

# /etc/mysql/mysql.conf.d/mysqld.cnf (Debian/Ubuntu, exemple)
[mysqld]
max_allowed_packet = 128M

Pour MariaDB, c’est identique (fichier dépend de la distro). Après modification :

sudo systemctl restart mysql
# ou
sudo systemctl restart mariadb

Référence : MySQL max_allowed_packet (doc officielle MySQL).

Étape 3 : aligner wait_timeout et les timeouts applicatifs

Sur la plupart des stacks WordPress classiques (PHP-FPM sans connexion persistante), wait_timeout n’est pas le premier suspect. Mais dès que vous avez :

  • DB distante (cloud) + idle timeouts réseau,
  • proxy SQL,
  • ou un plugin qui garde un process PHP vivant (workers longs, CLI, queue),

…un wait_timeout trop bas devient visible.

Exemple :

# Exemple : 120s est souvent trop bas pour des opérations admin lourdes.
[mysqld]
wait_timeout = 300
interactive_timeout = 300

Ne montez pas à 8 heures “par confort” si vous avez beaucoup de connexions : vous gardez des connexions dormantes plus longtemps, ce qui peut augmenter la pression mémoire côté DB.

Étape 4 : PHP-FPM (souvent la vraie limite)

Si PHP-FPM tue la requête, le client voit 502/504, et WordPress peut loguer des erreurs DB “secondaires”. Vérifiez :

  • request_terminate_timeout (tue le worker après X secondes)
  • max_execution_time (php.ini)
  • pool saturation : pm.max_children, pm.max_requests

Exemple (à adapter) :

# /etc/php/8.3/fpm/pool.d/www.conf (version PHP selon votre serveur)
; Tuer un worker bloqué au bout de 120s
request_terminate_timeout = 120

; Éviter les workers "infinis" si un plugin part en boucle
pm.max_requests = 500

Référence PHP-FPM : PHP-FPM configuration.

Étape 5 : Nginx/Apache/Proxy (timeouts de lecture)

Si Nginx coupe la réponse pendant qu’un import tourne, vous aurez un client déconnecté, et parfois une DB laissée dans un état intermédiaire.

Exemples Nginx (reverse proxy) :

# /etc/nginx/conf.d/site.conf (exemple)
fastcgi_read_timeout 120;
fastcgi_send_timeout 120;

# Si proxy_pass vers un upstream
proxy_read_timeout 120;
proxy_send_timeout 120;

Le piège : vous augmentez Nginx à 300s, mais PHP-FPM est à 120s. Le proxy attend, PHP a déjà tué le worker. Alignez.

Étape 6 (avancé) : exécuter les gros jobs via WP-CLI, pas via HTTP

Pour les imports et reconstructions lourdes, le meilleur “serveur fix” est souvent organisationnel : sortir du cycle HTTP. Exemple :

# Cron système (toutes les 5 minutes)
*/5 * * * * cd /var/www/site && /usr/bin/wp cron event run --due-now --quiet

Référence WP-CLI : wp cron event run.


Vérifications après correction

  • Reproduisez l’action qui cassait (import, sauvegarde builder, cron).
  • Surveillez Query Monitor : plus d’erreur DB, et les requêtes les plus lentes doivent baisser (ou au moins être stables).
  • Vérifiez wp-content/debug.log (si activé) : absence de nouvelles entrées “gone away”.
  • Côté MySQL : plus d’Aborted connection corrélées au même endpoint.
  • Côté PHP-FPM : pas de workers tués sur la fenêtre de test.

Tableau de diagnostic rapide

Symptôme Cause probable Vérification Solution
Erreur 2006 sur import max_allowed_packet trop bas Logs MySQL + taille payload (options/transients) Augmenter max_allowed_packet + chunking
Erreur 2013 pendant une requête Requête lente / IO saturée Slow query log, PROCESSLIST Indexer, optimiser requête, réduire autoload
502/504 côté proxy Timeout Nginx/ALB/Cloudflare Logs Nginx/Proxy, timings Aligner timeouts proxy/PHP-FPM
Échec aléatoire admin Pool PHP-FPM saturé Logs PHP-FPM, métriques CPU/RAM Ajuster pm.max_children, caching, réduire jobs
Erreurs après migration Paramètres MySQL plus stricts SHOW VARIABLES, diff config Adapter max_allowed_packet / timeouts

Si ça ne marche toujours pas

Voici une procédure que j’utilise en production quand le bug est intermittent.

  1. Isoler l’endpoint : URL front, action admin, route REST, tâche cron. Notez l’heure exacte (timezone serveur).
  2. Désactiver en session isolée avec Health Check :
    • testez sans plugins ;
    • puis réactivez par groupes (cache, builder, SEO, e-commerce).
  3. Vérifier la console navigateur (builder, REST) :
    • statut 502/504 = proxy/PHP-FPM ;
    • statut 200 mais JSON invalide = réponse tronquée (timeout/kill).
  4. Activer SAVEQUERIES 5 minutes sur staging, reproduire, puis inspecter les requêtes lentes (attention à l’impact perf).
  5. Contrôler wp_options : autoload trop gros = pages plus lentes = plus de timeouts.
    • Repérez les options autoload énormes.
  6. Contrôler les verrous : sur WooCommerce/Action Scheduler, vérifiez les tables d’actions et les jobs coincés.
  7. Vider les caches (dans l’ordre) :
    • cache plugin (page/object),
    • OPcache (si vous avez accès),
    • CDN/proxy (Cloudflare),
    • cache navigateur (pour les artefacts JS/CSS).
  8. Vérifier la réécriture si le symptôme passe par REST :
    • Régénérez les permaliens (sans changer, juste “Enregistrer”).
  9. Si DB distante : testez la latence réseau et les idle timeouts (NAT/LB). Les “gone away” peuvent venir d’un équipement réseau, pas de MySQL.

Point de vigilance : beaucoup de gens testent directement en production “pour gagner du temps”, puis déclenchent une tempête (rebuild cache + import + cron). Faites vos tests lourds sur staging, puis déployez les réglages.


Pièges et erreurs courantes

Symptôme / erreur Cause probable Solution recommandée
“MySQL server has gone away” après avoir collé un snippet Snippet copié au mauvais endroit, ou erreur PHP (point-virgule manquant) qui casse l’exécution et laisse croire à un souci DB Activer WP_DEBUG sur staging, corriger la syntaxe, utiliser un plugin de snippets fiable ou un mu-plugin
Erreur uniquement dans l’éditeur Elementor/Divi AJAX/REST timeout, réponse tronquée Aligner timeouts proxy/PHP-FPM + chunking des traitements déclenchés à la sauvegarde
Le bug disparaît après purge cache puis revient Cache froid = plus de requêtes = plus lent = timeout Optimiser requêtes, autoload, object cache persistant (Redis), limiter jobs
“Allowed memory size exhausted” puis “gone away” OOM PHP, worker tué, DB “victime collatérale” Corriger la boucle/traitement, profiler, augmenter mémoire seulement si nécessaire
Après mise à jour d’un plugin d’import Changement de comportement (requêtes plus grosses, plus d’items par batch) Réduire batch size côté plugin, vérifier max_allowed_packet
Hook inadapté (ex: traitement lourd sur init) Le code s’exécute sur chaque requête front/admin Déplacer vers une action admin explicite ou un cron asynchrone
Conflit avec cache objet Object cache persistant mal configuré, timeouts Redis, ou sérialisation énorme Vérifier logs Redis, limiter taille transients, ajuster TTL

Erreurs de mise en œuvre que je vois souvent :

  • Oublier de retirer un filtre temporaire (ex: posts_where) : vous ralentissez tout le site.
  • Confondre action et filtre et déclencher un traitement lourd au mauvais moment.
  • Priorité de hook : un plugin de cache exécute tôt, votre code arrive après et invalide tout.
  • Code d’un vieux tutoriel : requêtes directes non préparées, gros get_posts avec posts_per_page = -1, pas de pagination.
  • Tester sur production sans sauvegarde : une modification MySQL mal calibrée peut impacter tout le cluster.

Variante / alternative

Méthode sans code (plugin / réglages)

  • Query Monitor pour identifier la page/action qui déclenche les requêtes lentes.
  • Health Check pour confirmer un conflit plugin/thème sans impacter les visiteurs.
  • Si vous utilisez WooCommerce : inspectez les tâches planifiées (Action Scheduler) et réduisez la concurrence si votre hébergeur est limité.

Méthode plus avancée (dev/ops)

  • Mettre en place un object cache persistant (Redis) pour réduire la pression DB, surtout sur sites builders avec beaucoup d’options.
  • Activer le slow query log et corriger les index (souvent sur wp_postmeta(meta_key) et requêtes custom).
  • Externaliser les jobs lourds (imports, rebuild) dans un worker CLI supervisé (systemd, queue), au lieu de WP-Cron.

Éviter ce problème à l’avenir

  • Évitez les requêtes “tout” :
    • pas de get_posts( posts_per_page => -1 ) en production pour des tables volumineuses ;
    • préférez pagination, curseur ID, ou WP_Query chunké.
  • Limitez la taille des options autoload : beaucoup de lenteurs viennent de autoload = yes sur des options énormes. Réservez l’autoload aux options nécessaires à chaque requête.
  • Ne stockez pas des blobs géants en options/transients si vous pouvez stocker en fichiers, tables dédiées, ou cache objet.
  • Alignez les timeouts (proxy, PHP-FPM, MySQL) et documentez-les. Les “gone away” intermittents viennent souvent d’un seul timeout plus court que les autres.
  • Surveillez :
    • erreurs DB dans les logs,
    • slow queries,
    • redémarrages MySQL,
    • saturation PHP-FPM.

Si vous maintenez un plugin : ajoutez des garde-fous (taille max payload, batch size configurable, reprise, logs). C’est la différence entre “ça passe sur mon local” et “ça tient en prod”.


Ressources


Questions fréquentes

Quelle est la différence entre l’erreur 2006 et 2013 ?

2006 signifie que le serveur a déjà fermé la connexion quand PHP tente d’envoyer la requête. 2013 indique souvent une perte de connexion pendant l’exécution (requête lente, réseau, proxy).

Augmenter WP_MEMORY_LIMIT règle-t-il le problème ?

Parfois, vous évitez un crash PHP qui déclenchait une cascade. Mais “MySQL gone away” est majoritairement un problème de timeouts/paquets/requêtes. Augmenter la mémoire sans corriger la cause augmente la charge et peut empirer la stabilité.

Pourquoi ça arrive surtout pendant un import ?

Parce que l’import cumule : requêtes massives, transactions longues, IO disque, et parfois payloads énormes (images, meta sérialisée). Si vous le faites en une seule requête HTTP, vous êtes presque garanti de heurter un timeout quelque part.

Est-ce lié à WordPress 6.9.4 ?

Non, pas spécifiquement. WordPress 6.9.4 subit les limites de l’infra comme les versions précédentes. Ce qui change, c’est souvent votre stack (PHP 8.1+, proxy, DB managée) et la complexité des plugins/builders.

Que dois-je régler en premier côté serveur ?

Dans la pratique : max_allowed_packet (si payloads), puis l’alignement des timeouts proxy ↔ PHP-FPM ↔ MySQL. Ensuite seulement, la capacité (CPU/RAM/IO) si vous êtes saturé.

Un cache objet Redis peut-il éviter “gone away” ?

Oui, indirectement : moins de requêtes DB, donc moins d’IO et moins de risque de requêtes lentes. Mais si vous stockez des objets énormes ou si Redis est instable, vous remplacez un problème par un autre.

Pourquoi ça marche en local mais pas en production ?

En local, vous avez souvent des timeouts plus permissifs, pas de proxy, une latence nulle, et une DB peu chargée. En production, le moindre traitement long rencontre des limites (pool PHP-FPM, proxy, DB partagée, quotas hébergeur).

Elementor/Divi “cassent” la DB ?

Non. Mais ils augmentent la complexité des pages, la quantité d’options, et la charge admin (sauvegardes lourdes). Ils rendent les timeouts plus probables si l’infra est limite ou si un plugin ajoute un traitement lourd au mauvais hook.

Dois-je activer les connexions persistantes MySQL en PHP ?

Je le déconseille sur la plupart des hébergements WordPress classiques : cela complique le dimensionnement côté MySQL et peut amplifier les problèmes de connexions dormantes. Préférez chunking + jobs CLI.

Comment prouver que c’est MySQL qui redémarre ?

Corrélez l’heure de l’erreur WordPress avec les logs MySQL/MariaDB (messages de restart/crash recovery) ou les événements du provider (RDS/Cloud SQL). Si vous voyez un redémarrage, le “gone away” est une conséquence, pas la cause.