Chapter 10

MySQL vs PostgreSQL 与生产实践

技术选型、备份恢复、权限管理、连接池、读写分离与分库分表

10.1 MySQL vs PostgreSQL 详细对比

特性MySQL 8.0PostgreSQL 16
诞生年份19951996(源自 1986 POSTGRES)
开源协议GPL(企业版商业)PostgreSQL License(完全自由)
事务InnoDB 引擎支持,MyISAM 不支持全面支持,DDL 也可在事务中
默认隔离级别REPEATABLE READREAD COMMITTED
JSON 支持支持(5.7+),功能较简单原生 JSON + JSONB(二进制,可建索引)
全文搜索内置 FULLTEXT 索引,功能有限内置 tsvector/tsquery,多语言分词
数组类型不支持原生支持(可存储数组并索引)
窗口函数8.0+ 支持从很早版本支持,更完整
CTE 递归8.0+ 支持完全支持
物化视图不原生支持原生支持 + CONCURRENTLY 刷新
外键InnoDB 支持完整支持,更严格检查
读写性能读写均衡,简单查询极快复杂查询优化器更强
并发MVCC,适合高并发读MVCC,复杂并发场景表现更好
扩展性分库分表工具成熟(ShardingSphere)逻辑复制、Citus 水平扩展
存储引擎多引擎(InnoDB/MyISAM/Memory)单引擎,但可扩展插件
社区活跃度极高,国内文档丰富高,国际社区更活跃

10.2 何时选 MySQL,何时选 PostgreSQL

🐬

选 MySQL 的场景 互联网公司传统 Web 后端;国内运维团队熟悉 MySQL;需要与 LAMP/LEMP 生态集成;简单 CRUD 为主,极致读写性能;已有成熟的 MySQL 技术团队。

🐘

选 PostgreSQL 的场景 需要复杂查询和分析(BI/报表);使用 JSON 存储灵活数据;需要完整 SQL 标准支持;地理数据(PostGIS 扩展);需要物化视图、数组、枚举等高级特性;新项目启动推荐。

10.3 备份与恢复

MySQL 备份

# mysqldump:逻辑备份(导出 SQL 语句)
mysqldump -u root -p blog_db > blog_db_backup.sql

# 备份所有数据库
mysqldump -u root -p --all-databases > all_dbs_backup.sql

# 只备份表结构(不含数据)
mysqldump -u root -p --no-data blog_db > blog_db_schema.sql

# 压缩备份(节省空间)
mysqldump -u root -p blog_db | gzip > blog_db_$(date +%Y%m%d).sql.gz

# 恢复数据库
mysql -u root -p blog_db < blog_db_backup.sql

PostgreSQL 备份

# pg_dump:逻辑备份(纯文本 SQL 格式)
pg_dump -U postgres blog_db > blog_db_backup.sql

# pg_dump:自定义格式(支持并行恢复,更推荐)
pg_dump -U postgres -Fc blog_db > blog_db_backup.dump

# 并行恢复(-j 4 表示 4 个并行进程,恢复更快)
pg_restore -U postgres -d blog_db -j 4 blog_db_backup.dump

# 备份所有数据库(包括角色、表空间配置)
pg_dumpall -U postgres > all_dbs_backup.sql

# 恢复纯文本格式
psql -U postgres blog_db < blog_db_backup.sql
💡

逻辑备份 vs 物理备份 逻辑备份(mysqldump/pg_dump):导出 SQL 语句,可读性好,支持跨版本迁移,但备份/恢复速度慢。物理备份(Percona XtraBackup / pg_basebackup):直接复制数据文件,速度快,适合生产环境大规模备份,但不能跨主要版本迁移。

10.4 用户权限管理

SQL-- MySQL:创建用户
CREATE USER 'app_user'@'%' IDENTIFIED BY 'StrongPassword123!';
-- @'%' 表示允许从任意主机连接;@'localhost' 表示只允许本机

-- 授予最小权限(最小权限原则!)
GRANT SELECT, INSERT, UPDATE, DELETE
  ON blog_db.*
  TO 'app_user'@'%';

-- 只读用户(适合报表、BI 工具)
CREATE USER 'readonly_user'@'%' IDENTIFIED BY 'ReadOnlyPass!';
GRANT SELECT ON blog_db.* TO 'readonly_user'@'%';

-- 刷新权限(MySQL 可能需要)
FLUSH PRIVILEGES;

-- 撤销权限
REVOKE INSERT, UPDATE, DELETE ON blog_db.* FROM 'app_user'@'%';

-- 删除用户
DROP USER 'app_user'@'%';

-- PostgreSQL:创建角色和用户
CREATE ROLE app_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_role;

CREATE USER app_user WITH PASSWORD 'StrongPassword123!';
GRANT app_role TO app_user;
⚠️

最小权限原则(Principle of Least Privilege) 应用程序的数据库用户永远不应该使用 root/superuser!只授予业务所需的最小权限。DBA 用高权限账户,应用用只读/读写账户,各司其职。这是数据库安全的基本原则。

10.5 连接池

每次创建数据库连接都需要 TCP 握手、认证等过程,耗时约 5-20ms。高并发时频繁创建/销毁连接会成为性能瓶颈。连接池预先创建一组连接,复用它们,避免重复开销。

连接池适用模式说明
PgBouncerPostgreSQL独立代理进程最流行的 PostgreSQL 连接池,支持 session/transaction/statement 三种模式
ProxySQLMySQL代理服务器除了连接池,还支持读写分离、查询路由
应用层连接池所有内嵌在应用如 HikariCP(Java)、node-postgres pool(Node.js)、SQLAlchemy pool(Python)

PgBouncer 配置示例

# pgbouncer.ini 核心配置
[databases]
blog_db = host=127.0.0.1 port=5432 dbname=blog_db

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432           # 应用连接 PgBouncer 的端口
pool_mode = transaction      # 事务模式:每次事务复用连接(推荐)
max_client_conn = 1000       # 允许的最大客户端连接数
default_pool_size = 20       # 每个数据库的连接池大小(真实到 PG 的连接数)
min_pool_size = 5

10.6 读写分离

读写分离是提高数据库承载能力的经典架构:主库(Master)处理写操作,从库(Replica)处理读操作。

📊

主从复制原理
主库将所有修改操作写入 Binary Log(Binlog);从库通过网络拉取 Binlog 并重放,使自身数据与主库一致。这个过程存在轻微延迟(Replication Lag),通常几毫秒到几秒不等。

从库延迟问题及应对

10.7 分库分表

当单表数据量超过 5000 万行,或单库 TPS 达到瓶颈时,需要考虑分库分表。

↕️

垂直分片 按业务模块拆分:用户库、商品库、订单库分放不同数据库服务器。减少单库压力,各模块独立。跨库 JOIN 变难,需在应用层处理。

↔️

水平分片(Sharding) 同一张表按行拆分到多个数据库:如 orders 表按 user_id % 4 分到 4 个库。每个分片数据量减小,可扩展。分布式查询、跨片 JOIN、全局 ID 生成都变复杂。

⚠️

分库分表是最后的手段 在考虑分库分表之前,应先充分利用:索引优化、慢查询优化、读写分离、缓存(Redis)、归档历史数据等方案。分库分表带来的运维复杂度和开发成本极高。

10.8 常见性能问题与解决方案清单

问题现象可能原因解决方案
某查询突然变慢 数据量增长、统计信息过期、锁等待 EXPLAIN 分析、ANALYZE TABLE 更新统计、查看锁等待
全表扫描(type=ALL) 缺少索引、索引失效 添加合适索引,检查 WHERE 条件是否破坏索引
CPU 飙升 慢查询堆积、大量排序/聚合 慢查询日志找出耗时 SQL,添加索引,优化 ORDER BY
磁盘 I/O 高 buffer pool 太小、大量随机 I/O 增大 innodb_buffer_pool_size(建议内存的 70%)
连接数耗尽 连接泄漏、并发连接太多 使用连接池、检查未关闭连接的代码
死锁频繁 事务加锁顺序不一致 统一加锁顺序、缩短事务、降低隔离级别
主从延迟大 大事务、DDL 操作、从库配置弱 拆分大事务、从库配置与主库一致、启用并行复制
表空间持续增长 数据不断增加、碎片化 归档历史数据、定期 OPTIMIZE TABLE、分区表
大表 ALTER 超时 ALTER TABLE 锁表 MySQL 用 pt-online-schema-change 在线 DDL;PostgreSQL 用 pg_repack

生产环境关键配置参数

# MySQL InnoDB 关键参数(my.cnf)
innodb_buffer_pool_size = 8G        # 缓冲池:建议物理内存的 60-80%
innodb_log_file_size = 512M         # Redo Log 文件大小,影响写入性能
innodb_flush_log_at_trx_commit = 1  # 1=每次提交刷盘(最安全)
sync_binlog = 1                     # 每次提交同步 binlog(最安全)
max_connections = 500               # 最大连接数(配合连接池使用)
slow_query_log = ON
long_query_time = 1                 # 超过 1 秒记入慢查询日志

# PostgreSQL 关键参数(postgresql.conf)
shared_buffers = 4GB                # 共享缓冲区:建议内存的 25%
work_mem = 64MB                     # 单个排序/哈希操作的内存(每连接)
max_connections = 200               # 配合 PgBouncer 使用
checkpoint_completion_target = 0.9  # 平滑 checkpoint,减少 I/O 峰值
wal_buffers = 64MB                  # WAL 写入缓冲
log_min_duration_statement = 1000   # 慢查询日志阈值(毫秒)

课程总结

🎓

恭喜完成全部10章的学习!
你已经掌握了:关系数据库基础理论 → DDL 建表设计 → DML 增删改查 → JOIN 多表查询 → 聚合与窗口函数 → 索引优化 → 事务 ACID → 视图存储过程 → 范式设计 → 生产实践。

下一步建议:在真实项目中应用所学(哪怕是个人项目);阅读 MySQL/PostgreSQL 官方文档深化某个专题;学习 Python/Node.js/Go 中操作数据库的最佳实践(ORM vs 裸 SQL)。