WordPress e l’errore sull’ottimizzazione DB


Quando gli aggiornamenti non sono backward compatibili.


Per chi ha un sito WordPress stand alone, la manutenzione dei database è una delle attività che gli competono. Proprio oggi mentre stavo svolgendo questa operazione, sono incappato in una backward compatibility trap.

Praticamente la manutenzione base del db di WordPresse prevede, tra l’altro, la rimozione di tutte le copie dei post creati con le bozze. Ad ogni modifica viene creato un post fantasma, ossia c’è nel db ma non è visibile sul sito. Non avete idea di quanto spazio possono occupare in breve tempo, specialmente se siete di quelli che scrivono i post direttamente in ambiente WordPress, senza creare il post con un tool e pubblicarlo solo quando il post è stato scritto, editato, verificato e confermato.

Nell’esempio parlerò di una installazione monoblog. Per la versione multi blog aggiungerò dei dettagli in coda.

Di norma per pulire le nostre vecchie, ed inutili versioni, le operazioni da fare sono le seguenti:

  1. collegarsi al database
  2. verificare se esistano di questi post
  3. cancellarli
  4. eseguire una ottimizzazione della tabella in questione.

che tradotto in operazioni reali diventano, considerando nel nostro caso che il database di chiami WordPress ed il suffisso alle tabelle sia wp_):

ssh utente@nostroserver
mysql -u utente-del-db-wordpress -p
Password:*******
mysql> use WordPress;

Tutti i record della tabella wp_post che hanno il campo post_type contenente la parola “revision” sono revisioni di un post già pubblicato.

mysql> select count(*) from wp_posts where post_type="revision";

Se la risposta è 

+----------+
| count(*) |
+----------+
|        0 |
+----------+
1 row in set (0.00 sec)

Allora, fortunelli, non avete post fantasma in giro per la tabella, ma se il valore è maggiore di zero, come quasi sicuramente sarà, dovrete cancellarli con :

mysql> delete from wp_posts where post_type="revision";
Query OK, 999 rows affected (0.02 sec)

Chiaramente il 999 è solo un numero indicativo dei record cancellati.

A questo punto eseguiamo l’ottimizazione della tabella in questione

mysq> optimize table wp_post;
+----------------------+----------+----------+-----------------------------------------------------------------+
| Table                | Op       | Msg_type | Msg_text                                                        |
+----------------------+----------+----------+-----------------------------------------------------------------+
| WordPress.wp_posts | optimize | note     | Table does not support optimize, doing recreate + analyze instead |
| WordPress.wp_posts | optimize | error    | Invalid default value for 'post_date'                             |
| WordPress.wp_posts | optimize | status   | Operation failed                                                  |
+----------------------+----------+----------+-----------------------------------------------------------------+

Che diavolo è successo? Principalmente due cose: una che non ci crea problemi l’altra invece si!!

La prima riga ci segnala che la tabella wp_post non supporta l’ottimizzazione. Questo va bene: la tabella wp_post è stata creata quasi certamente in formato InnoDB ed optimize non funziona, se non sulle tabelle create in formato MyISAM.

Se invochiamo optimize table in MySQL su una tavola InnoDB riceveremo questa notifica, e MySQL da solo procederà ad una operazione, alternativa, per ottenere lo stesso risultato, ossia, creare una nuova tabella, caricarci i dati dalla vecchia wp_posts e cancellare la vecchia. In questo modo viene eseguita l’ottimizzazione sulle tabelle in formato InnoDB e MySQL lo fa per noi.

Ma c’è un secondo problema. Se avessimo invocato l’ottimizzazione avremmo ricevuto in risposta la stessa prima riga ed una seconda riga con lo status OK a segnalare che le operazioni sopra descritte, MySQL era riuscito a completarle senza problemi. Invece qui la seconda riga ci segnala un errore aggiuntivo:

optimize | error | Invalid default value for ‘post_date’

e qui si presenta il problema: la backward compatibility trap di cui parlavo prima.

WordPress crea la tabella wp_posts in questo modo:

mysql > CREATE TABLE `wp_posts` (
    ->   `ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
    ->   `post_author` bigint(20) unsigned NOT NULL DEFAULT '0',
    ->   `post_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
    ->   `post_date_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
    ->   `post_content` longtext NOT NULL,
    ->   `post_title` text NOT NULL,
    ->   `post_excerpt` text NOT NULL,
    ->   `post_status` varchar(20) NOT NULL DEFAULT 'publish',
    ->   `comment_status` varchar(20) NOT NULL DEFAULT 'open',
    ->   `ping_status` varchar(20) NOT NULL DEFAULT 'open',
    ->   `post_password` varchar(20) NOT NULL DEFAULT '',
    ->   `post_name` varchar(200) NOT NULL DEFAULT '',
    ->   `to_ping` text NOT NULL,
    ->   `pinged` text NOT NULL,
    ->   `post_modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
    ->   `post_modified_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
    ->   `post_content_filtered` longtext NOT NULL,
    ->   `post_parent` bigint(20) unsigned NOT NULL DEFAULT '0',
    ->   `guid` varchar(255) NOT NULL DEFAULT '',
    ->   `menu_order` int(11) NOT NULL DEFAULT '0',
    ->   `post_type` varchar(20) NOT NULL DEFAULT 'post',
    ->   `post_mime_type` varchar(100) NOT NULL DEFAULT '',
    ->   `comment_count` bigint(20) NOT NULL DEFAULT '0',
    ->   PRIMARY KEY (`ID`),
    ->   KEY `post_name` (`post_name`(191)),
    ->   KEY `type_status_date` (`post_type`,`post_status`,`post_date`,`ID`),
    ->   KEY `post_parent` (`post_parent`),
    ->   KEY `post_author` (`post_author`)
    -> ) ENGINE=innodb AUTO_INCREMENT=536 DEFAULT CHARSET=utf8;

E fino alla versione di MySQL 5.6 tutto andava bene. Ma dalla versione 5.7 in poi, MySQL ha inserito nei parametri di sql_mode anche NO_ZERO_IN_DATE, NO_ZERO_DATE che impedisce, o quanto meno, non accetta per validi campi di tipo datetime che abbiano un default value settato a ‘0000–00–00 00:00:00’. e guardate un po’ nelle definizioni di creazione della tabella wp_post cosa abbiamo????

`post_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`post_date_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',

e più giù

`post_modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`post_modified_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',

da qui l’errore nella seconda riga della notifica ottenuta dal comando optimize ossia: | optimize | error | Invalid default value for ‘post_date’.

Fortunatamente WordPress, di suo, non crea mai date con tale valore a 0, ma usa la data più vecchia rappresentabile per lui, ossia 1970–01–01 01:01:01, non chiedetemene il motivo: c’è ma esula da questo post. Se tentasse di usare una data a zero scatterebbe la trap di NO_ZERO_IN_DATE quindi non riuscirebbe ad usarla, almeno dalla versione 5.7 di MySQL in poi.

Come risolvere il probelma: ci son diversi modi:

  1. modificare il valore di default della tabella da 0000–00–00 00:00:00 a 1970–01–01 01:01:01 per tutti campi data.
  2. modificare, temporaneamente, il settaggio di sql_mode, e ripristinarlo a fine ottimizzazione — nel qual caso vi consiglio di scrivervi la procedura per non diventare matti alla prossima ottimizzazione della tabella in questione — 
  3. modificare il valore di sql_mode in modo permanente — cosa che personalmente sconsiglio, altrimenti vi troverete ad usare una versione non standard di MySQL e questo, in futuro, doveste avere problemi di debugging potrebbe crearvi grossi fastidi!!!

I ho optato per la seconda soluzione per vari motivi tra i quali: cosa succede se cambio il valore di defaul della tabella di WordPress? Che ricadute avrà sull’intero sistema WP? Intendo plugin in particolare, ma anche temi ed altro.

Indi la soluzione per la modifica, ribadisco, temporanea del settaggio del parametro sql_mode:

mysql> set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' ;

in questo modo disattiviamo, temporaneamente, i parametri NO_ZERO_IN_DATE, NO_ZERO_DATE della sql_mode; a questo punto eseguiamo la nostra ottimizzazione:

mysql> optimize table wp_posts;
+--------------------+----------+----------+-------------------------------------------------------------------+
| Table                | Op       | Msg_type | Msg_text                                                        |
+--------------------+----------+----------+-------------------------------------------------------------------+
| WordPress.wp_posts | optimize | note     | Table does not support optimize, doing recreate + analyze instead |
| WordPress.wp_posts | optimize | status   | OK                                                                |
+--------------------+----------+----------+-------------------------------------------------------------------+

Come potetre vedere, adesso abbiamo in riga uno la notifica che non si può usare il comando optimize su un database in formato InnoDB, ma in seconda riga abbiamo come status OK. Questo sta a significare che le operazioni eseguite da MySQL per ottimizzare una tabella InnoDB sono andate a buon fine.

Resta solo da ripristinare i valori corretti di sql_mode: lo potete fare in due modi:

  • eseguire quanto segue in console: 
sql_mode=ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION

      e proseguire le vostre attività in MySQL

  • o uscire dal client MySQL per ripristinare il valore originale di sql_mode così che contenga nuovamente anche i parametri NO_ZERO_IN_DATE, NO_ZERO_DATE.

Ed il gioco è fatto!!!