缓慢的 Odoo 实例会导致生产力下降和用户沮丧,从而造成经济损失。好消息:大多数 Odoo 性能问题无需升级硬件即可解决。坏消息:诊断根本原因需要了解整个堆栈——Python、PostgreSQL、Nginx、Redis 和网络层。
属于我们的性能与可扩展性系列
阅读完整指南缓慢的 Odoo 实例会导致生产力下降和用户沮丧,从而造成经济损失。好消息:大多数 Odoo 性能问题无需升级硬件即可解决。坏消息:诊断根本原因需要了解整个堆栈——Python、PostgreSQL、Nginx、Redis 和网络层。
本指南涵盖了 Odoo 19 Enterprise 的完整性能优化生命周期:识别瓶颈、调整 PostgreSQL、优化 Odoo 服务器设置、配置 Nginx 缓存以及根据用户数量和交易量调整基础设施规模。
要点
- PostgreSQL 调优可带来最大的性能提升(典型安装中为 50-300%)
- 共享缓冲区应设置为可用 RAM 的 25% 作为起点
- Odoo 的 ORM 生成可以用 pg_stat_statements 捕获的 N+1 查询
- 频繁过滤字段(company_id、state、date)的索引是强制性的
- Nginx 代理缓存无需访问 Odoo 服务器即可提供静态资源
- Worker配置直接影响并发用户容量
- Redis会话缓存减少了身份验证的数据库负载
- 必须针对高写入 Odoo 工作负载调整清理和分析计划
诊断性能瓶颈
在调整任何内容之前,先确定时间实际花在哪里。盲目优化浪费精力。
启用 Odoo 查询日志记录:
# odoo.conf
[options]
log_level = info
logfile = /var/log/odoo/odoo.log
# For SQL query logging (development/staging only):
log_handler = odoo.sql_db:DEBUG
启用 PostgreSQL 慢查询日志记录:
# /etc/postgresql/15/main/postgresql.conf
log_min_duration_statement = 1000 # Log queries taking > 1 second
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '
log_checkpoints = on
log_connections = on
log_lock_waits = on
安装pg_stat_statements(最有价值的PostgreSQL扩展):
-- Enable the extension
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- Find the top 20 slowest queries
SELECT
round(total_exec_time::numeric, 2) AS total_ms,
calls,
round(mean_exec_time::numeric, 2) AS mean_ms,
round((100 * total_exec_time / sum(total_exec_time) OVER ())::numeric, 2) AS pct,
left(query, 100) AS query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;
-- Reset statistics after tuning to measure improvement
SELECT pg_stat_statements_reset();
在 Odoo 中识别 N+1 查询:
当 Odoo 加载记录列表,然后对每个记录进行一次查询以获取相关数据时,会发生 N+1 查询。在 pg_stat_statements 中查找类似这样的模式:
-- This query appearing 500 times in a single page load = N+1 problem
SELECT * FROM res_partner WHERE id = $1
修复方法是使用 Odoo 的 prefetch_ids 机制或将 select 添加到 ORM 查询中:
# Bad: triggers N+1 for partner on each order
for order in orders:
print(order.partner_id.name) # One query per order
# Good: prefetch partner data in one query
orders = self.env['sale.order'].search([...])
orders.mapped('partner_id') # Forces prefetch
for order in orders:
print(order.partner_id.name) # No additional queries
PostgreSQL 配置调优
PostgreSQL 附带了保守的默认值,旨在在任何硬件上运行。对于 Odoo 生产服务器,必须调整这些默认值。
内存设置(对于 32 GB RAM 服务器):
# /etc/postgresql/15/main/postgresql.conf
# Shared buffers: 25% of RAM
shared_buffers = 8GB
# Work memory: per-operation memory for sorts/joins
# Start conservative, increase if you see disk sorts
work_mem = 64MB
# Maintenance work memory: for VACUUM, CREATE INDEX
maintenance_work_mem = 2GB
# Effective cache size: tells planner how much OS cache is available
# Set to 75% of total RAM
effective_cache_size = 24GB
# WAL settings for better write performance
wal_buffers = 256MB
checkpoint_completion_target = 0.9
checkpoint_timeout = 15min
max_wal_size = 4GB
min_wal_size = 1GB
连接设置:
# Maximum connections (Odoo workers × 2 + headroom)
max_connections = 200
# For connection pooling with PgBouncer
# If using PgBouncer, reduce to 50-100
查询计划器设置:
# Enable parallel query execution
max_parallel_workers_per_gather = 4
max_parallel_workers = 8
max_worker_processes = 16
# SSD storage: random_page_cost should equal seq_page_cost
random_page_cost = 1.1 # Default is 4.0 (for spinning disk)
seq_page_cost = 1.0
# Increase statistics target for better query plans on Odoo's large tables
default_statistics_target = 200
针对高写入工作负载的 Autovacuum 调整:
Odoo 的库存、会计和消息传递模块会产生大量 INSERT/UPDATE 流量。默认 autovacuum 设置不够:
autovacuum = on
autovacuum_max_workers = 5
autovacuum_naptime = 30s
autovacuum_vacuum_threshold = 50
autovacuum_analyze_threshold = 50
autovacuum_vacuum_scale_factor = 0.01 # Vacuum when 1% of rows are dead
autovacuum_analyze_scale_factor = 0.005 # Analyze when 0.5% of rows change
autovacuum_vacuum_cost_delay = 2ms # Reduce I/O throttling
关键数据库索引
缺少索引是 Odoo 性能问题的第二大常见原因,仅次于配置不当。 Odoo 为主键和一些外键创建索引,但许多常用过滤字段缺少索引。
使用 pg_missing_fk_indexes 视图检查丢失的索引:
-- Find foreign keys without indexes
SELECT
tc.table_name,
kcu.column_name,
ccu.table_name AS foreign_table_name,
pg_relation_size(tc.table_name::regclass) AS table_size
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage ccu
ON ccu.constraint_name = tc.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY'
AND NOT EXISTS (
SELECT 1 FROM pg_indexes pi
WHERE pi.tablename = tc.table_name
AND pi.indexdef LIKE '%' || kcu.column_name || '%'
)
ORDER BY table_size DESC;
Odoo 19 的基本索引:
-- Sale orders (most queried table)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_sale_order_state
ON sale_order(state);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_sale_order_company_date
ON sale_order(company_id, date_order DESC);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_sale_order_partner
ON sale_order(partner_id) WHERE state != 'cancel';
-- Account moves (invoicing)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_account_move_state_type
ON account_move(state, move_type);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_account_move_company_date
ON account_move(company_id, invoice_date DESC);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_account_move_partner
ON account_move(partner_id) WHERE state = 'posted';
-- Account move lines (most queried for reconciliation)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_account_move_line_account_reconcile
ON account_move_line(account_id, reconciled, date);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_account_move_line_move_date
ON account_move_line(move_id, date);
-- Stock moves (inventory)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_stock_move_state_product
ON stock_move(state, product_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_stock_quant_product_location
ON stock_quant(product_id, location_id);
-- Mail messages (can grow very large)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_mail_message_res_model_id
ON mail_message(res_model, res_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_mail_message_date
ON mail_message(date DESC);
-- IR rule performance (access control)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ir_rule_model_groups
ON ir_rule(model_id);
Odoo Worker 配置
Odoo 工作线程的数量决定了服务器可以处理的并发请求数。
工人计数公式:
Workers = (CPU_cores × 2) + 1
Memory per worker: 256MB - 512MB depending on workload
Example for 8 CPU cores, 32GB RAM:
Workers = (8 × 2) + 1 = 17
Memory check: 17 × 512MB = 8.5GB (well within 32GB)
odoo.conf 工作进程设置:
[options]
# Worker processes
workers = 17
# Limits to prevent runaway workers
limit_memory_hard = 2684354560 # 2.5GB hard limit (kills worker)
limit_memory_soft = 2147483648 # 2GB soft limit (triggers gc)
limit_time_cpu = 600 # CPU seconds per request
limit_time_real = 1200 # Wall clock seconds per request
limit_request = 8192 # Requests before worker restart
# Long polling (for live notifications)
longpolling_port = 8072
了解工人类型:
Odoo 使用两种类型的工作人员:
- HTTP工作人员(
workers配置):处理所有网络请求 - Cron工人(保留1个):在后台运行计划的操作
cron 工作线程始终运行,但不计入您的 HTTP 容量。确保即使在峰值负载下也至少有 1 个 cron 工作线程可用。
Nginx 性能配置
Nginx 位于 Odoo 前面,负责处理 TLS 终止、静态文件服务和可选的缓存。
高性能Nginx配置:
upstream odoo {
server 127.0.0.1:8069 weight=1 fail_timeout=0;
}
upstream odoochat {
server 127.0.0.1:8072 weight=1 fail_timeout=0;
}
# Cache zone for static assets
proxy_cache_path /var/cache/nginx/odoo
levels=1:2
keys_zone=odoo_cache:100m
max_size=1g
inactive=60m
use_temp_path=off;
server {
listen 443 ssl http2;
server_name your-odoo.com;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 1000;
gzip_comp_level 6;
# Static file caching
location /web/static/ {
proxy_cache odoo_cache;
proxy_cache_valid 200 7d;
proxy_cache_use_stale error timeout updating
http_500 http_502 http_503 http_504;
add_header X-Cache-Status $upstream_cache_status;
expires 7d;
proxy_pass http://odoo;
}
# Long polling for live chat/notifications
location /web/longpolling {
proxy_pass http://odoochat;
proxy_read_timeout 3600s;
proxy_connect_timeout 300s;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Main application
location / {
proxy_pass http://odoo;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 720s;
proxy_connect_timeout 300s;
proxy_buffering on;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
# Security headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy strict-origin-when-cross-origin;
}
}
Redis 用于会话和缓存
Redis 显着降低了会话管理和 Odoo ORM 缓存的数据库负载。
安装和配置Redis:
# Install Redis
sudo apt install redis-server
# Configure Redis for Odoo (max 4GB memory, LRU eviction)
sudo nano /etc/redis/redis.conf
# redis.conf
maxmemory 4gb
maxmemory-policy allkeys-lru
save "" # Disable persistence for pure cache
tcp-keepalive 300
配置 Odoo 使用 Redis:
# odoo.conf
[options]
# Redis for session storage
session_redis_host = 127.0.0.1
session_redis_port = 6379
session_redis_prefix = odoo_session_
# Redis for IR rules and ORM cache
cache_redis_host = 127.0.0.1
cache_redis_port = 6379
监控和持续绩效管理
设置pgBadger进行PostgreSQL日志分析:
# Install pgBadger
sudo apt install pgbadger
# Generate report from PostgreSQL logs
pgbadger /var/log/postgresql/postgresql-15-main.log \
-o /var/www/html/pgbadger/index.html \
--format html \
--top 20
要监控的关键指标:
| 公制 | 警告阈值 | 临界阈值 |
|---|---|---|
| 页面加载时间 | > 2 秒 | > 5 秒 |
| 数据库查询时间 | > 平均 100 毫秒 | > 平均 500 毫秒 |
| 工人记忆 | > 限制的 80% | > 极限值的 95% |
| PostgreSQL 连接 | > 最大值的 70% | > 最大值的 90% |
| 磁盘 IOPS | > 80% 的配置 | > 95% 的配置 |
| 缓存命中率 | < 95% | < 90% |
常见问题
50 个并发 Odoo 用户的最低服务器规格是多少?
对于具有中等交易量的 50 个并发用户:8 个 vCPU、32 GB RAM、500 GB SSD(首选 NVMe)。 Odoo 的 PostgreSQL 数据库是主要的 I/O 瓶颈,因此快速存储比原始 CPU 速度更重要。配置 13 个工作线程(8×2-3 的预留空间)、8 GB 共享缓冲区,并确保您的数据库位于 SSD 卷上。
如何诊断我的 Odoo 慢是 Python 还是 PostgreSQL?
使用 Odoo 的内置分析器(开发人员模式下的设置 → 技术 → 性能 → 分析器)来记录缓慢的操作。火焰图将显示时间是花费在 Python 代码上还是等待 SQL 结果。如果 SQL 查询占主导地位,请重点关注 PostgreSQL 调优和索引。如果 Python 占主导地位,请查找是否缺少 @api.depends 缓存或自定义代码效率低下。
我应该使用 PgBouncer 进行连接池吗?
是的,适用于具有 30 多个 Odoo 工作人员或大量 API 流量的部署。事务模式池中的 PgBouncer 允许许多 Odoo 工作人员共享较小的实际 PostgreSQL 连接池,从而减少每个连接的开销。使用 PgBouncer 时,将 PostgreSQL 中的 max_connections 配置为 50-100,然后设置 PgBouncer 的 pool_size 以匹配您的 Odoo 工作线程数。
我应该多久在 Odoo 数据库上运行 VACUUM ANALYZE 一次?
如果配置正确,Autovacuum 会自动处理此问题。经过上述调整(激进的比例因子,更多的工作人员)后,autovacuum 应该在活动表上连续运行。运行 SELECT schemaname, tablename, last_vacuum, last_autovacuum, last_analyze, last_autoanalyze FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 20; 来验证表是否经常被清理。
Odoo 工作人员过多会产生什么影响?
每个 Odoo 工作线程至少消耗 256–512 MB RAM。太多的worker会导致内存耗尽,导致worker崩溃(limit_memory_hard),从而导致用户出现HTTP 500错误。此外,太多的 PostgreSQL 连接(workers × max_db_connections)可能会压垮数据库。从公式(CPU×2+1)开始,监视负载下的内存,并根据需要向下调整。
后续步骤
Odoo 性能调优是一个迭代过程。单个调整会话可以带来显着的收益,但持续的性能需要随着数据量的增长进行持续监控、定期索引分析和配置调整。
ECOSIRE 为企业部署提供 Odoo 性能审计,为您的特定工作负载、事务模式和基础设施确定影响最大的优化。我们的工程师已将 Odoo 安装从 10 个用户的中小型企业部署调整为 500 个用户的企业部署。
分享您当前的服务器规格、用户数量以及您遇到的症状,我们的团队将确定根本原因并提供优先优化计划。
作者
ECOSIRE Research and Development Team技术写作
ECOSIRE 技术写作团队的内容涵盖 Odoo ERP、Shopify 电子商务、AI 智能体、Power BI 分析、GoHighLevel 自动化以及企业软件最佳实践。我们的指南帮助企业做出明智的技术决策。
相关文章
AI 支持的客户细分:从 RFM 到预测聚类
了解 AI 如何将客户细分从静态 RFM 分析转变为动态预测聚类。使用 Python、Odoo 和真实 ROI 数据的实施指南。
用于供应链优化的人工智能:可见性、预测和自动化
利用人工智能改变供应链运营:需求感知、供应商风险评分、路线优化、仓库自动化和中断预测。 2026年指南。
B2B 批发电商指南:买家门户、账期与客户专属定价
打造买家真正愿意用的 B2B 批发门户:客户专属定价、账期与信用额度、采购单与审批结账,以及基于订单历史的一键复购。
更多来自性能与可扩展性
Webhook 调试和监控:完整的故障排除指南
通过这份涵盖故障模式、调试工具、重试策略、监控仪表板和安全最佳实践的完整指南掌握 Webhook 调试。
k6 负载测试:在发布之前对您的 API 进行压力测试
掌握 Node.js API 的 k6 负载测试。涵盖虚拟用户启动、阈值、场景、HTTP/2、WebSocket 测试、Grafana 仪表板和 CI 集成模式。
Nginx 生产配置:SSL、缓存和安全性
Nginx 生产配置指南:SSL 终止、HTTP/2、缓存标头、安全标头、速率限制、反向代理设置和 Cloudflare 集成模式。
在生产中测试和监控 AI 代理
在生产环境中测试和监控 AI 代理的完整指南。涵盖 OpenClaw 部署的评估框架、可观测性、漂移检测和事件响应。
使用 OpenClaw 进行合规性监控代理
部署 OpenClaw AI 代理以进行持续合规性监控。自动执行监管检查、策略执行、审计跟踪生成和合规性报告。
优化 AI 代理成本:令牌使用和缓存
通过令牌优化、缓存、模型路由和使用情况监控来降低 AI 代理运营成本的实用策略。生产 OpenClaw 部署真正节省了成本。