🎯 Ce que cet article apprend
- Se connecter en
psqlà une base Odoo et explorer le schéma avec\dt,\d,\du,\dx - Maîtriser une dizaine de SELECT essentiels au quotidien et lire un plan
EXPLAIN ANALYZEpour profiler une requête lente - Backup et restore Odoo 19 (formats ZIP natif, custom
-Fc, SQL plain) sans corrompre la base ni perdre le filestore
Cet article clôt la série S01 — Boîte à outils Linux du dev Odoo. Les quatre précédents ont posé le socle système, le contrôle de version, l'automatisation et l'accès ORM en shell. Le présent épisode descend d'un cran plus bas, dans le moteur PostgreSQL lui-même. Toutes les commandes sont vérifiées sous Ubuntu 24.04 LTS avec PostgreSQL 16 et Odoo 19 Community.
1. Se connecter à PostgreSQL — psql et configuration
Le client psql est l'outil officiel d'accès interactif à PostgreSQL. Sur une sandbox Odoo typique, l'utilisateur PG est odoo et le serveur écoute sur localhost:5432.
# Connexion locale à une base Odoo
psql -U odoo -h localhost -d ma_base_dev
# Lister toutes les bases visibles par l'utilisateur courant
psql -U odoo -h localhost -d postgres -l
# Connexion en mode peer (utilisateur Unix = utilisateur PG, sans password)
sudo -u postgres psql
# Version du serveur et de psql
psql --version
Pour éviter de retaper le mot de passe, le fichier ~/.pgpass centralise les credentials au format host:port:database:user:password, en chmod 600 obligatoire (sans quoi psql l'ignore silencieusement).
echo "localhost:5432:*:odoo:motdepasse" >> ~/.pgpass
chmod 600 ~/.pgpass
psql -U odoo -h localhost -d ma_base_dev
L'authentification PG elle-même est définie dans pg_hba.conf (sur Ubuntu 24.04 : /etc/postgresql/16/main/pg_hba.conf). La méthode trust y autorise toute connexion sans mot de passe : tolérable sur une sandbox dev isolée, jamais sur serveur exposé.
📖 Définition — pg_hba.conf
pg_hba.conf (host-based authentication) est le fichier de config d'authentification PostgreSQL. Chaque ligne associe à une combinaison (type, database, user, address, method) la règle d'autorisation. Méthodes principales : md5 / scram-sha-256 (mot de passe haché), peer (utilisateur Unix local = utilisateur PG), trust (aucune vérification). La règle trust n'a aucune place sur un serveur de production : tout processus local peut alors se connecter en tant que postgres et lire ou écrire n'importe où.
🔧 Encadré v19 — PostgreSQL et psycopg2
PG minimum Odoo 19 : PG 13 (source : odoo/release.py, MIN_PG_VERSION = 13). Ubuntu 24.04 LTS livre PG 16, parfaitement supporté. Côté Python, le driver reste psycopg2 en v19, pas de migration psycopg3 prévue (sql_db.py l.23, requirements.txt l.55-58). Versions épinglées : 2.9.2 sur Py 3.10, 2.9.5 sur 3.11, 2.9.9 sur 3.12 (Noble), 2.9.10 sur 3.13.
2. Explorer le schéma Odoo — méta-commandes psql
Une base Odoo standard contient plus d'un millier de tables. Les méta-commandes psql (préfixe antislash) sont l'outil de navigation par excellence pour explorer la structure réelle sans quitter le terminal.
\l -- Liste des bases du cluster
\c ma_base_dev -- Se connecter à une autre base sans quitter psql
\dt -- Toutes les tables du schéma public
\dt res_* -- Filtrer par préfixe (toutes les tables res_xxx)
\dt account_* -- Toutes les tables de la comptabilité
\d res_partner -- Structure complète d'une table + index
\d+ res_partner -- Idem + commentaires de colonnes (très utile métier)
\df -- Toutes les fonctions (triggers, calculs)
\du -- Utilisateurs et rôles PostgreSQL
\dn -- Schémas (Odoo n'utilise que public par défaut)
\dx -- Extensions installées (pg_trgm, unaccent, postgis…)
\timing on -- Active le chronométrage de chaque requête
\watch 2 -- Réexécute la dernière requête toutes les 2 secondes
\x -- Bascule en affichage vertical (lignes longues)
\q -- Quitter psql
La commande \d+ res_partner affiche en bas l'éventuel commentaire de chaque colonne — Odoo y stocke parfois la help du champ. Plus rapide que fouiller le code source pour comprendre un modèle natif.
\d res_partner dans psql affiche la structure complète de la table : colonnes typées (id integer, name character varying, email character varying, country_id integer…), valeurs par défaut, contraintes NOT NULL, puis liste des index et clés étrangères en pied. Aucun aller-retour vers le code source nécessaire pour vérifier la structure réelle.2.1. Dix SELECT utiles au quotidien dev
Les requêtes ci-dessous couvrent les diagnostics les plus fréquents : audit modules, recherche de doublons, santé du cluster, repérage des champs custom. Toutes en lecture seule, exécutables sans crainte sur n'importe quelle base — y compris en production pour audit.
-- 1. Modules installés et version effective
SELECT name, state, latest_version
FROM ir_module_module
WHERE state = 'installed'
ORDER BY name;
-- 2. Top 10 des tables les plus volumineuses
SELECT relname,
pg_size_pretty(pg_total_relation_size(oid)) AS taille
FROM pg_class
WHERE relkind = 'r'
ORDER BY pg_total_relation_size(oid) DESC
LIMIT 10;
-- 3. Doublons de partner par email (audit nettoyage CRM)
SELECT email, COUNT(*) AS n
FROM res_partner
WHERE email IS NOT NULL
GROUP BY email
HAVING COUNT(*) > 1
ORDER BY n DESC
LIMIT 20;
-- 4. Bons de commande créés sur les 7 derniers jours
SELECT name, partner_id, amount_total, state
FROM sale_order
WHERE create_date >= NOW() - INTERVAL '7 days'
ORDER BY create_date DESC;
-- 5. Utilisateurs actifs jamais connectés
SELECT u.login, p.name
FROM res_users u
JOIN res_partner p ON p.id = u.partner_id
WHERE u.active = true
AND u.login_date IS NULL;
-- 6. Pièces jointes orphelines (sans res_id ou res_id = 0)
SELECT id, name, mimetype, res_model, file_size
FROM ir_attachment
WHERE res_id IS NULL OR res_id = 0
ORDER BY file_size DESC NULLS LAST
LIMIT 20;
-- 7. Champs custom déclarés (state = 'manual' = créés via UI Studio)
SELECT model, name, ttype
FROM ir_model_fields
WHERE state = 'manual'
ORDER BY model, name;
-- 8. Vues actives non héritées par modèle (audit personnalisation)
SELECT model, name, type
FROM ir_ui_view
WHERE active = true AND inherit_id IS NULL
ORDER BY model
LIMIT 30;
-- 9. Crons planifiés et leur prochain déclenchement
SELECT cron_name, active, nextcall, interval_number, interval_type
FROM ir_cron
ORDER BY nextcall NULLS LAST;
-- 10. Sessions PostgreSQL actives (debug deadlock ou cron coincé)
SELECT pid, usename, datname, state, query_start, LEFT(query, 80) AS query
FROM pg_stat_activity
WHERE state != 'idle' AND pid <> pg_backend_pid()
ORDER BY query_start;
La requête 2 remonte les tables qui pèsent : mail_message et mail_tracking_value arrivent presque toujours en tête sur une base de plus de six mois. La requête 6 repère les pièces jointes orphelines — sources fréquentes de filestore gonflé, candidates au garbage collector _gc_file_store. La requête 9 utilise bien le champ cron_name (et non name), stocké calculé sur ir_cron en v19.
3. Garde-fou central — pourquoi UPDATE et DELETE directs sont des pièges
Section centrale de l'épisode. Tout dev Odoo qui découvre psql est tenté, un jour, de corriger une donnée par un UPDATE SQL direct. Le geste paraît anodin. Il ne l'est pas.
⚠️ Piège majeur — UPDATE et DELETE SQL directs sur une base Odoo
Un UPDATE res_partner SET name = … WHERE … exécuté en psql bypass intégralement la couche ORM Python. Concrètement :
- Cache ORM non invalidé. Le serveur garde l'ancienne valeur en mémoire jusqu'au redémarrage du worker ou à un
invalidate_modelexplicite. L'utilisateur voit l'ancien, le SQL voit le nouveau. - Contraintes Python bypassées. Toute règle déclarée via
@api.constrainsest silencieusement sautée. - Chatter et tracking absents. Aucune entrée
mail.message, aucun audit. La modification devient invisible à l'historique métier. - Audit logs OCA contournés. Le module
auditlogne reçoit aucun signal. DELETEdirect laisse des pièces jointes orphelines. Lesir_attachmentassociées restent dans le filestore. Le job_gc_file_storeles nettoie un jour, mais avec délai.DELETEsansWHEREn'a aucune protection native. Une faute de frappe vide une table entière en une milliseconde, sans confirmation ni corbeille.
Règle absolue : aucun UPDATE ni DELETE direct sur une base de production. En dev uniquement, avec backup préalable et transaction explicite. Toujours préférer la voie ORM (env['model'].write(…) ou unlink()) sauf cas extrême documenté.
Le pattern minimal pour tester une suppression : enveloppe transactionnelle explicite, SELECT COUNT(*) de vérification avant/après, ROLLBACK par défaut sauf validation manuelle.
BEGIN;
-- 1. Compter avant
SELECT COUNT(*) FROM sale_order
WHERE state = 'draft' AND date_order < '2024-01-01';
-- 2. Lancer la suppression candidate
DELETE FROM sale_order
WHERE state = 'draft' AND date_order < '2024-01-01';
-- 3. Vérifier le compte impacté
SELECT COUNT(*) FROM sale_order
WHERE state = 'draft' AND date_order < '2024-01-01';
-- 4. Annuler par défaut, ou COMMIT explicite si tout est conforme
ROLLBACK;
-- COMMIT;
Tant que COMMIT n'est pas envoyé, rien n'est persisté ; ROLLBACK annule tout. Ce filet doit devenir réflexe sur toute opération destructive — même en sandbox. Attention : une transaction laissée ouverte reste visible dans pg_stat_activity en état idle in transaction et peut bloquer d'autres connexions. Ne jamais laisser un terminal psql avec transaction ouverte plus de quelques minutes.
4. Le pattern propre — UPDATE SQL massif via l'ORM
Le cas légitime existe : opération massive sur des millions d'enregistrements où l'ORM ramerait des heures, migration ponctuelle, fix urgent documenté. Le pattern à employer est strictement encadré et détaillé dans l'épisode S04 sur odoo-bin shell.
# À exécuter dans odoo-bin shell, jamais en psql brut
env['res.partner'].flush_model(['name'])
env.cr.execute(
"UPDATE res_partner SET name = UPPER(name) WHERE country_id = %s",
(dz_id,)
)
env['res.partner'].invalidate_model(['name'])
env.cr.commit()
L'enchaînement est non négociable : flush_model écrit d'abord en base les modifications ORM en attente sur le champ ciblé, le UPDATE SQL passe via le curseur Odoo (jamais un psycopg2 ouvert manuellement), invalidate_model force le cache à recharger, cr.commit() valide la transaction (le shell n'auto-commit pas). Sans ces étapes, la désynchronisation cache / base est garantie.
5. pg_dump et pg_restore — formats et procédures
Trois formats coexistent côté Odoo 19. Le choix dépend du contexte : restauration rapide DBA, archive lisible, ou backup complet avec filestore.
# --- Format custom (binaire compressé, format DBA) ---
# Avantage : compact, restaurable sélectivement (table par table)
# Inconvénient : ne contient PAS le filestore
pg_dump -Fc --no-owner -f backup_$(date +%Y%m%d).dump nom_base
# Restaurer dans une base neuve
psql -U odoo -h localhost -d postgres \
-c "CREATE DATABASE nom_base_cible;"
pg_restore --no-owner -d nom_base_cible backup_20260513.dump
# --- Format SQL plain (texte lisible) ---
# Avantage : versionnable, diffable, modifiable à la main
# Inconvénient : volumineux (3 à 5x le format custom)
pg_dump --no-owner -f backup.sql nom_base
psql -U odoo -h localhost -d nom_base_cible -f backup.sql
# --- Format ZIP natif Odoo (web GUI ou API) ---
# Avantage : inclut dump.sql + filestore/ + manifest.json
# Seul format à inclure le filestore en un seul livrable
curl -X POST -F "master_pwd=admin" \
-F "name=nom_base" -F "backup_format=zip" \
-o backup_complet.zip \
http://localhost:8069/web/database/backup
L'option --no-owner est cruciale : elle omet les commandes ALTER OWNER et permet de restaurer sur un cluster où le rôle propriétaire diffère (par exemple odoo_prod côté source, odoo côté cible). Sans elle, la restauration échoue sur la première instruction ALTER ... OWNER TO odoo_prod si ce rôle n'existe pas sur la cible.
Le format ZIP natif Odoo est le seul à inclure le filestore — format à privilégier pour une migration complète entre serveurs. Le format custom -Fc reste plus rapide et compact pour un point de restauration DBA ou un audit.
6. DROP / CREATE / template — recréer une base de test propre
Détruire et recréer une base de test en quelques secondes est un superpouvoir du dev Odoo. Le pattern repose sur les templates PostgreSQL natifs.
# Méthode rapide via heredoc bash
psql -U odoo -h localhost -d postgres << EOF
DROP DATABASE IF EXISTS sandbox_dev;
CREATE DATABASE sandbox_dev TEMPLATE template_odoo_clean;
EOF
Le pattern TEMPLATE consiste à préparer une fois pour toutes une base template_odoo_clean (Odoo fraîchement initialisé, modules essentiels, zéro donnée métier) puis à la marquer comme template PG.
-- Préparer la base modèle (une seule fois)
UPDATE pg_database SET datistemplate = true
WHERE datname = 'template_odoo_clean';
-- Vérifier
SELECT datname, datistemplate FROM pg_database
WHERE datistemplate = true;
Deux contraintes : la base template doit avoir datistemplate = true, et aucune connexion ne doit y être ouverte au moment du CREATE DATABASE ... TEMPLATE. Toute session psql active fait échouer la copie avec source database is being accessed by other users. Réflexe : \c postgres avant de lancer la copie.
7. Performance dev — EXPLAIN ANALYZE et diagnostic d'une requête lente
Une vue Odoo qui prend plusieurs secondes à charger trahit presque toujours une requête SQL non indexée générée par un compute ou un domaine. L'outil de diagnostic est EXPLAIN ANALYZE, qui exécute réellement la requête et affiche le plan mesuré.
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT *
FROM sale_order
WHERE partner_id = 7 AND state = 'sale'
ORDER BY date_order DESC;
Le plan affiche, pour chaque nœud (scan, jointure, tri, agrégation), le coût estimé, le coût mesuré, les lignes attendues vs réelles, et la consommation de buffers PG. Les éléments à repérer :
Seq Scansur une table volumineuse alors qu'une colonne duWHEREaurait pu utiliser un index — index manquant.Buffers: shared read=Nélevé — donnée non en cache, première lecture disque. Une seconde exécution doit basculer enshared hit.- Écart entre
rows estimatedetrows actual— statistiques obsolètes,ANALYZE nom_table;les rafraîchit. Execution Timeen bas — mesure de vérité à comparer avant / après index.
Réflexe d'optimisation : un Seq Scan sur sale_order avec WHERE partner_id = X AND state = Y appelle un index composite.
-- Créer un index composite ciblé
CREATE INDEX ON sale_order(partner_id, state);
-- Mettre à jour les statistiques
ANALYZE sale_order;
-- Relancer EXPLAIN ANALYZE pour mesurer le gain
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM sale_order
WHERE partner_id = 7 AND state = 'sale'
ORDER BY date_order DESC;
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) sur une requête sale_order. Le premier plan affiche un Seq Scan avec Execution Time: 412 ms. Après création de l'index composite (partner_id, state), le second plan bascule en Index Scan avec Execution Time: 3 ms. Le gain de 137x est immédiatement mesurable.💡 Astuce — \timing et \watch pour observer un cron en temps réel
Activer \timing dans psql chronomètre chaque requête en millisecondes, affiché juste après le résultat. Combiné à \watch 2 (réexécution automatique toutes les deux secondes), l'outil devient un moniteur temps réel. Cas concret : SELECT cron_name, nextcall FROM ir_cron WHERE active LIMIT 5; \watch 5 donne l'instant exact où un cron part en exécution, sans rafraîchissement manuel.
8. Les patterns psql / dump / EXPLAIN en synthèse
Récapitulatif par cas d'usage. Mémoriser le cas permet de retrouver la commande sans hésiter, même sous pression.
| Cas d'usage | Commande | Notes |
|---|---|---|
| Connexion locale Odoo | psql -U odoo -h localhost -d ma_base | Avec ~/.pgpass chmod 600 |
| Lister les bases | psql -U odoo -l | Ou \l en interactif |
| Structure d'une table | \d res_partner | \d+ ajoute les commentaires |
| Tables d'un préfixe | \dt res_* | Joker SQL standard |
| Top tables par taille | SELECT … pg_total_relation_size … | Audit disque immédiat |
| Modules installés | SELECT name FROM ir_module_module WHERE state = 'installed' | Lecture seule, sans risque |
| Pièces jointes orphelines | SELECT … FROM ir_attachment WHERE res_id IS NULL | Candidates GC filestore |
| Sessions PG actives | SELECT … FROM pg_stat_activity WHERE state != 'idle' | Debug deadlock / cron |
| Transaction de sécurité | BEGIN; … ROLLBACK; | Obligatoire avant DELETE |
| Dump format custom | pg_dump -Fc --no-owner -f f.dump base | Compact, sans filestore |
| Dump format SQL plain | pg_dump --no-owner -f f.sql base | Lisible, volumineux |
| Dump format ZIP Odoo | curl POST /web/database/backup | SEUL format avec filestore |
| Restaurer custom | pg_restore --no-owner -d cible f.dump | Sur base vide créée préalablement |
| Recréer base via template | CREATE DATABASE x TEMPLATE template_odoo_clean | Aucune connexion ouverte sur template |
| Profiler une requête | EXPLAIN (ANALYZE, BUFFERS) SELECT … | Cherche les Seq Scan |
| Créer index ciblé | CREATE INDEX ON table(col1, col2) | Puis ANALYZE table; |
| Chrono requêtes | \timing on | Affiche ms après chaque requête |
| Monitoring temps réel | \watch 2 | Réexécute toutes les 2 secondes |
Cinq épisodes, cinq outils. La série Boîte à outils Linux du dev Odoo couvre l'arsenal minimum pour piloter une sandbox Odoo 19 — du système Linux au SQL PostgreSQL en passant par Git, Bash et l'ORM en shell. Chaque pattern a été vérifié sur Ubuntu 24.04 LTS avec Odoo 19 Community et PostgreSQL 16. La maîtrise combinée de ces cinq couches transforme la posture du développeur Odoo : plus de dépendance aveugle à l'interface web, plus d'incident sans diagnostic possible.
9. Et après — passage en production avec l'ebook E3 Déploiement
📘 La marche suivante — du sandbox à la production
La sandbox de développement est désormais maîtrisée : Linux, Git, Bash, shell ORM, PostgreSQL — cinq outils, cinq épisodes, une boîte à outils complète. Reste le passage en production.
Nginx reverse proxy, certificats SSL Let's Encrypt, supervision Prometheus/Grafana, backups chiffrés vers S3, monitoring Sentry, scaling workers HTTP et cron, hardening systemd et SSH, surveillance pg_stat_activity en continu — c'est un autre métier que celui de la sandbox dev. Les enjeux (disponibilité, intégrité, RPO/RTO) changent, les outils aussi.
L'ebook E3 — Déploiement Odoo 19 en production traite ce périmètre en profondeur, de la commande VPS à la routine de monitoring quotidien, avec scripts et procédures d'incident détaillées.
Ebook en finalisation. Pour être prévenu de sa sortie et bénéficier du tarif lancement, inscription via la newsletter OdooSkills.
Voir aussi dans la série
S01 — Linux : 30 commandes pour la sandbox
logs, processus, ports, permissions, systemd, alias .bashrc.
S02 — Git & GitHub : 20 commandes pour cloner, brancher, contribuer
clone shallow, submodules OCA, workflow feature-branch, PR via gh.
S03 — Bash : 5 scripts qui font gagner 1 heure par jour
start-odoo.sh, restore-db.sh, dump-and-clean.sh, install-and-test.sh, update-all-addons.sh.
S04 — odoo-bin shell : 15 patterns ORM essentiels
env, search_fetch v19, Domain, flush_model / invalidate_model.
S05 — PostgreSQL : psql, SELECT, dump propre
Requêtes psql, EXPLAIN ANALYZE, garde-fous UPDATE / DELETE, CTA E3.
Articles complémentaires
#57 — Gestion des bases de données Odoo 19
Théorie multi-bases Odoo — niveau au-dessus de psql brut.
#105 — Écrire des tests automatisés en Odoo 19
Tests TransactionCase / HttpCase — complément debugging du SQL direct.