三年前,我们团队把核心业务的数据库从MySQL迁移到了PostgreSQL 17。

那时候的我,对PostgreSQL的了解还停留在"它是一个功能强大的开源数据库"这个层面。觉得它和MySQL差不多,只是SQL语法有些不同,迁移过来应该很简单。

三年过去了,我在PostgreSQL上踩了很多坑,也积累了很多经验。从最开始的不适应,到现在的得心应手,我对PostgreSQL有了完全不同的理解。

这篇文章我想分享一下使用PostgreSQL三年的实战经验和感悟。从数据库设计、性能优化到运维管理,聊聊PostgreSQL教会我的那些事。如果你正在用PostgreSQL,或者打算从其他数据库迁移到PostgreSQL,希望这篇文章能帮你少走一些弯路。

先说明一下,本文基于PostgreSQL 17及以上版本,部分特性在更早的版本中可能不支持。

道理一:PostgreSQL不是MySQL的替代品,而是完全不同的物种

第一个道理,也是我花了最长时间才明白的,就是PostgreSQL不是MySQL的替代品,而是一个完全不同的物种。

刚迁移的时候,我总是用MySQL的思维来用PostgreSQL。比如,用MySQL的方式设计表,用MySQL的方式写SQL,用MySQL的方式做性能优化。结果就是,处处碰壁,性能还不如MySQL。

后来我才慢慢明白,PostgreSQL和MySQL虽然都是关系型数据库,但设计理念完全不同。

MySQL的设计理念是"简单、快速、易用"。它默认的配置就能跑得不错,对开发者很友好,不需要太多的数据库知识就能用好。MySQL的哲学是"让你先跑起来,其他的以后再说"。

PostgreSQL的设计理念是"强大、灵活、标准"。它提供了非常多的功能和特性,支持复杂的数据类型、高级SQL功能、丰富的扩展。但PostgreSQL的默认配置比较保守,需要根据实际情况进行调优,才能发挥出它的性能。PostgreSQL的哲学是"给你足够的工具和能力,你自己决定怎么用"。

举个例子,MySQL的自增主键很简单,就是AUTO_INCREMENT。PostgreSQL的自增主键有好几种方式,serial、bigserial、identity、sequence,各有各的特点和适用场景。刚开始用的时候,我都不知道该选哪个。

再比如,MySQL的字符串类型就是varchar和text,很简单。PostgreSQL的字符串类型有varchar、char、text,还有citext、ltree等扩展类型,功能更强大,但也更复杂。

所以,用PostgreSQL,不能用MySQL的思维,要学习PostgreSQL的思维。要了解它的设计理念,掌握它的特性,按照它的方式来设计和优化。只有这样,才能真正发挥出PostgreSQL的威力。

道理二:索引不是越多越好,合适的索引才是最好的

第二个道理,是关于索引的。

刚用PostgreSQL的时候,我觉得索引是万能的,查询慢了就加索引。结果就是,一个表加了十几个索引,查询性能还是不好,写入性能反而下降了。

后来我才明白,索引不是越多越好,合适的索引才是最好的。

PostgreSQL的索引类型很丰富,有B-tree、Hash、GiST、SP-GiST、GIN、BRIN等。不同的索引类型,适合不同的场景。用错了索引类型,不仅不能提升性能,反而可能拖慢查询。

比如,B-tree索引适合等值查询和范围查询,是最常用的索引类型。GIN索引适合数组、全文搜索、JSONB等复杂数据类型的查询。GiST索引适合地理空间、全文搜索等场景。BRIN索引适合超大表、数据按顺序存储的场景。

刚开始用的时候,我不管什么查询都加B-tree索引。后来发现,对于数组查询和JSONB查询,B-tree索引用不上,需要用GIN索引。对于全文搜索,需要用GiST或GIN索引。用对了索引类型,查询性能提升了几十倍。

另外,PostgreSQL还有一些高级索引特性,比如复合索引、表达式索引、部分索引、覆盖索引等。这些特性能解决很多特殊场景的性能问题,但需要根据实际情况来使用。

比如,复合索引的列顺序很重要,要把等值查询的列放在前面,范围查询的列放在后面。表达式索引可以对函数或表达式的结果建索引,比如对lower(email)建索引,实现不区分大小写的查询。部分索引只对满足条件的行建索引,能减小索引体积,提升查询效率。

所以,用PostgreSQL的索引,不能盲目加,要分析查询模式,选择合适的索引类型和索引方式。一个好的索引,比十个不好的索引都有用。

道理三:EXPLAIN是最好的老师,学会看执行计划

第三个道理,是关于性能优化的。

刚用PostgreSQL的时候,遇到查询慢,我就瞎猜,一会儿加索引,一会儿改SQL,效果时好时坏。后来一位老司机告诉我,性能优化不要猜,要看执行计划。

从那以后,我养成了一个习惯,遇到慢查询,先EXPLAIN ANALYZE,看执行计划,再针对性地优化。

PostgreSQL的EXPLAIN功能非常强大,能显示查询的执行计划、成本估计、实际执行时间、行数估计等信息。学会看执行计划,是PostgreSQL性能优化的基本功。

看执行计划,主要看几个点:

第一,看扫描方式。是顺序扫描还是索引扫描?如果是顺序扫描,说明没有用到索引,可能需要加索引,或者索引没有生效。

第二,看连接方式。是嵌套循环连接、哈希连接还是合并连接?不同的连接方式适合不同的场景,如果连接方式不对,可能需要调整查询或者加索引。

第三,看行数估计。估计的行数和实际的行数差多少?如果差距很大,说明统计信息不准确,可能需要ANALYZE更新统计信息。

第四,看执行时间。哪个节点耗时最长?找到耗时最长的节点,针对性地优化。

刚开始看执行计划的时候,我也看不懂,觉得很复杂。但看多了,慢慢就有感觉了。现在看到一个执行计划,我大概能判断出问题出在哪里,该怎么优化。

另外,PostgreSQL还有一些辅助工具,比如pgstatstatements,可以统计慢查询;auto_explain,可以自动记录慢查询的执行计划。这些工具能帮你找到慢查询,分析性能问题。

所以,用PostgreSQL,一定要学会看执行计划。EXPLAIN是最好的老师,它会告诉你查询为什么慢,该怎么优化。不要猜,要看。

道理四:事务和锁是PostgreSQL的核心,必须搞懂

第四个道理,是关于事务和锁的。

刚用PostgreSQL的时候,我对事务和锁的理解很肤浅,觉得就是BEGIN和COMMIT的事。结果在高并发场景下,经常遇到死锁、锁等待、数据不一致等问题,搞得焦头烂额。

后来我花了很多时间研究PostgreSQL的事务和锁机制,才慢慢搞明白。

PostgreSQL的事务隔离级别有四种:读未提交、读已提交、可重复读、可串行化。默认是读已提交。不同的隔离级别,解决的并发问题不同,性能也不同。

刚用的时候,我用默认的读已提交,遇到了不可重复读和幻读的问题。后来才知道,对于一些重要的业务场景,需要用可重复读甚至可串行化的隔离级别,才能保证数据的一致性。

PostgreSQL的锁机制也很复杂,有表级锁、行级锁、 advisory锁等。不同的操作,会加不同的锁。锁的冲突关系也很复杂,需要仔细研究。

比如,SELECT语句会加AccessShareLock,CREATE INDEX会加ShareLock,ALTER TABLE会加AccessExclusiveLock。这些锁之间的冲突关系,决定了并发操作的兼容性。如果不了解锁的冲突关系,很容易在高并发下遇到锁等待的问题。

还有行级锁,SELECT FOR UPDATE、SELECT FOR SHARE等,会对行加锁。在高并发更新的场景下,如果加锁顺序不对,很容易出现死锁。

我踩过的一个坑是,在一个事务里,先更新表A,再更新表B;另一个事务里,先更新表B,再更新表A。结果两个事务互相等待,形成死锁。后来我统一了加锁顺序,所有事务都按相同的顺序更新表,死锁问题就解决了。

所以,用PostgreSQL,必须搞懂事务和锁。这是PostgreSQL的核心,也是高并发场景下必须掌握的知识。不搞懂事务和锁,在高并发下一定会出问题。

道理五:VACUUM不是可选的,是必须的

第五个道理,是关于VACUUM的。

刚用PostgreSQL的时候,我不知道VACUUM是什么,也从来没运行过。结果数据库运行了一段时间之后,表越来越大,查询越来越慢,磁盘空间也不够用了。

后来才知道,PostgreSQL的MVCC(多版本并发控制)机制,会保留旧版本的数据。这些旧版本的数据,如果不及时清理,会越来越多,导致表膨胀,性能下降,磁盘空间耗尽。

VACUUM就是用来清理这些旧版本数据的。PostgreSQL有自动VACUUM(autovacuum),默认是开启的。但默认的autovacuum配置比较保守,对于写入量大的表,可能清理不及时,需要调整配置。

我踩过的一个坑是,有一个写入量很大的表,autovacuum跟不上,表膨胀到了原来的好几倍,查询性能严重下降。后来我手动运行了VACUUM FULL,回收了空间,查询性能才恢复。但VACUUM FULL会锁表,影响业务,只能在凌晨低峰期运行。

后来我调整了autovacuum的配置,对于写入量大的表,调小了autovacuumvacuumscale_factor,让autovacuum更频繁地运行。同时,也定期监控表的膨胀率,发现膨胀严重的表,及时处理。

另外,PostgreSQL 17对VACUUM做了很多优化,比如并行VACUUM、更高效的死元组清理等。升级到新版本,也能提升VACUUM的效率。

所以,用PostgreSQL,VACUUM不是可选的,是必须的。要了解VACUUM的机制,配置好autovacuum,监控表的膨胀率,及时处理膨胀问题。否则,数据库运行一段时间之后,一定会出性能问题。

道理六:扩展是PostgreSQL的灵魂,要用好

第六个道理,是关于扩展的。

PostgreSQL最强大的地方,不是它的核心功能,而是它的扩展生态。PostgreSQL有非常多的扩展,可以为数据库增加各种功能,比如地理空间、全文搜索、时序数据、图数据库等。

刚用PostgreSQL的时候,我只用核心功能,不知道扩展的存在。结果很多功能,我都在应用层自己实现,效率低,性能也不好。

后来我才发现,很多功能,PostgreSQL已经有成熟的扩展了,直接用就行,不需要自己造轮子。

比如,PostGIS扩展,为PostgreSQL增加了地理空间功能,可以存储和查询地理数据,做空间分析。如果你的业务涉及地理位置,用PostGIS比自己在应用层实现好太多了。

再比如,pgtrgm扩展,提供了 trigram 相似度匹配,可以做模糊查询和相似度搜索。对于简单的全文搜索场景,用pgtrgm就够了,不需要上Elasticsearch。

还有timescaledb扩展,把PostgreSQL变成时序数据库,适合存储和查询时间序列数据,比如监控数据、物联网数据等。用timescaledb,比自己设计时序表好太多了。

其他常用的扩展还有:pgstatstatements(统计慢查询)、uuid-ossp(生成UUID)、citext(不区分大小写的字符串)、ltree(树形结构)、hstore(键值对)、jsonb(JSON数据,核心功能)等。

当然,扩展也不是越多越好。每个扩展都会增加数据库的复杂度,有些扩展可能有性能开销或者安全风险。要根据实际需求,选择合适的扩展,不要为了用而用。

另外,安装扩展需要超级用户权限,有些云数据库可能不支持安装自定义扩展。在选择扩展之前,要确认你的数据库环境是否支持。

所以,用PostgreSQL,一定要了解扩展生态。扩展是PostgreSQL的灵魂,用好扩展,能大大提升开发效率和数据库能力。不要什么都自己造轮子,先看看有没有现成的扩展。

道理七:备份和高可用不是小事,要提前做好

第七个道理,是关于备份和高可用的。

刚用PostgreSQL的时候,我觉得备份和高可用是运维的事,开发不用管。结果有一次,数据库出了问题,数据丢失了一部分,才发现备份策略有问题,恢复起来很麻烦。

从那以后,我才意识到,备份和高可用不是小事,要提前做好。

PostgreSQL的备份方式有几种:

第一种是SQL转储,用pg_dump把数据库导出成SQL文件。这种方式简单可靠,适合小型数据库。但恢复时间长,不适合大型数据库。

第二种是文件系统级备份,直接复制数据目录。这种方式恢复快,但需要数据库停止运行,或者用连续归档的方式。

第三种是连续归档和时间点恢复(PITR),结合基础备份和WAL归档,可以恢复到任意时间点。这种方式适合大型数据库,是生产环境的推荐方案。

我现在的备份策略是,每天做一次基础备份,持续归档WAL日志,保留最近一个月的备份。同时,定期测试备份的可恢复性,确保备份是有效的。

高可用方面,PostgreSQL有流复制(Streaming Replication),可以搭建主从架构。主库写入,从库同步复制,主库出问题的时候,可以切换到从库。

流复制有同步复制和异步复制两种。同步复制能保证数据不丢失,但性能有影响;异步复制性能好,但主库出问题的时候可能丢失少量数据。根据业务的重要性,选择合适的复制方式。

另外,还有一些高可用工具,比如Patroni、repmgr等,可以自动进行故障切换,减少 downtime。对于重要的业务,建议用这些工具来管理高可用。

我踩过的一个坑是,主从切换的时候,应用的连接地址没有自动切换,导致服务中断了很久。后来用了连接池(比如PgBouncer)和虚拟IP,才解决了这个问题。

所以,用PostgreSQL,备份和高可用不是小事,要提前做好。不要等到出了问题才想起备份,那时候就晚了。定期测试备份的可恢复性,搭建好高可用架构,才能保证业务的稳定运行。

写在最后

用了三年PostgreSQL,我才明白这些道理。每一个道理,都是踩坑踩出来的经验。

PostgreSQL是一个强大而复杂的数据库,它不是MySQL的简单替代品,而是一个有自己设计理念和特性的物种。要用好PostgreSQL,需要学习它的思维,掌握它的特性,按照它的方式来设计和优化。

这三年里,我从最开始的不适应,到现在的得心应手,经历了很多,也成长了很多。PostgreSQL教会我的不仅是数据库技术,还有一种严谨、深入的工作态度。

如果你正在用PostgreSQL,或者打算用PostgreSQL,希望这篇文章能给你一些启发,帮你少走一些弯路。数据库是应用的基石,把数据库用好,应用才能稳定高效地运行。

最后用一句话来结束这篇文章:"数据库没有银弹,只有不断学习和实践。"

愿你在PostgreSQL的道路上,越走越顺。