现在MySQL 8和PostgreSQL 10已经发布,现在是重新审视两个主要的开源关系数据库如何相互竞争的好时机。
在这些版本之前,一般的看法是,虽然Postgres在功能集及血统方面更胜一筹,但MySQL在大规模并发读/写操作方面进行了更多的大规模测试。
但是随着最新版本的发布,两者之间的差距已大大缩小。
功能对比
下面看一下通常我们认为比较时髦
的功能。
特征 | MySQL8 | PostgreSQL10 |
---|---|---|
「查询和分析」 | ||
通用表达式(CTEs) | ✔ New | ✔ |
视窗功能 | ✔ New | ✔ |
「数据类型」 | ||
支持JSON | ✔ Improved | ✔ |
GIS / SRS | ✔ Improved | ✔ |
全文搜索 | ✔ | ✔ |
「扩展性」 | ||
逻辑复制 | ✔ | ✔ New |
半同步复制 | ✔ | ✔ New |
声明式分区 | ✔ | ✔ New |
过去常常说MySQL最适合在线交易,而PostgreSQL最适合分析过程,但是现在不再是如此局面。
通用表达式(CTEs)和窗口函数一直是多数人选择PostgreSQL的主要原因。但是现在,在同一个表中employees
引用对表进行递归遍历boss_id
,或者在排序结果中找到中间值(或50%百分位数),在MySQL上不再是问题。
PostgreSQL上的复制缺乏配置灵活性,这是Uber转向MySQL的原因。但是现在有了逻辑复制,可以通过使用更新版本的Postgres创建副本并切换到该副本来实现零停机时间升级。截断大型时序事件表中的陈旧分区也容易得多。
在功能方面,两个数据库现在彼此相同。
二者有什么区别?
现在有一个问题就是,我们到底是选择MySQL还是PostgreSQL,那选择的原因又是什么?
「生态系统」就是这些因素之一。MySQL拥有一个强大的生态系统,其中包含MariaDB,Percona,Galera等变体,以及InnoDB以外的其他存储引擎,但它们也可能令人不知所措。Postgres的高端选项有限,但是随着最新版本引入的新功能,这种情况将会改变。
「治理」是另一个因素。每个人都在担心甲骨文(或最初为SUN)收购MySQL时,他们会毁了该产品,但过去十年来情况并非如此。实际上,收购后发展加速。Postgres在工作治理和协作社区方面拥有悠久的历史。
「体系结构的基本原理」不会经常更改,但是值得值得我们回顾。
特征 | MySQL8 | PostgreSQL10 |
---|---|---|
架构 | 单进程 | 多进程 |
并发 | 多线程 | fork(2) |
表结构 | 聚合索引 | Heap(堆) |
页面压缩 | 透明 | TOAST |
更新 | 就地/回滚Segments | 仅追加/ Hot |
垃圾回收 | 清除线程 | 自动回收进程 |
Transaction Log | REDO Log (WAL) | WAL |
Replication Log | Separate (Binlog) | WAL |
进程对比线程
当Postgres fork一个子进程来建立连接时,每个连接最多需要10 MB的空间。与MySQL的“每次连接线程”模型相比,内存压力更大,后者在64位平台上,线程的默认堆栈大小为 256KB。(当然,线程局部排序缓冲区等可以使此开销的重要性降低,即使可以忽略不计,但仍然如此。)
即使写时复制会与父进程一起保存一些共享的,不变的内存状态,但是当您有1000个以上的并发连接时,作为基于进程的体系结构的基本开销会增加负担,并且它可能是最重要的开销之一能力计划的因素。
也就是说,如果您在30台服务器上运行Rails应用程序,其中每台服务器具有16个CPU内核和32个Unicorn worker,则您有960个连接。在所有应用程序中,可能只有不到0.1%会达到这个规模,但这是需要牢记的。
聚合索引对比堆(Heap)表
聚合索引是其中行被直接嵌入主键的B树结构内的表结构。(非聚合)堆(Heap)是规则表结构,其中填充了与索引分开的数据行。
使用聚合索引时,当您通过主键查找记录时,单个I / O将检索整行,而非聚集索引始终通过遵循引用至少需要两个I / O。由于外键引用和联接将触发主键查找,因此影响可能很大,这将占查询的绝大多数。
聚合索引的理论缺点是,在使用次级索引进行查询时,遍历树节点的次数是您首先遍历次级索引,然后遍历聚合索引(也是一棵树)的两倍。
但是,考虑到现代惯例,将自动递增的整数作为主键[1](称为代理键),几乎总是希望拥有聚合索引。如果您要执行很多操作ORDER BY id
来检索最新(或最旧)的N条记录,那就更是如此,我认为这适用于大多数记录。
Postgres不支持聚合索引,而MySQL(InnoDB)不支持堆(Heap)。但是,无论哪种方式,如果您有大量内存,则差异应该很小。
页面结构和压缩
Postgres和MySQL都具有基于页面的物理存储(8KB和16KB)。
PostgreSQL物理存储简介
页面结构看起来像上图所示。它包含一些标题,我们将在这里不进行介绍,但是它们包含有关页面的元数据。标头后面的项目是一个数组标识符,由(offset, length)
指向元组或数据行的对组成。请记住,在Postgres中,可以通过这种方式将同一记录的多个版本存储在同一页面中。
MySQL的表空间结构与Oracle的表空间结构相似,它具有段,范围,页和行的多个层次结构层。
它还为UNDO提供了一个单独的部分,称为“回退部分”。与Postgres不同,MySQL将在同一区域保留同一记录的多个版本。
在两个数据库上,一行必须适合一个页面,这意味着一行必须小于8KB。(MySQL的页面中必须至少包含2行,巧合的是16KB / 2 = 8KB)
那么当列中有一个大的JSON对象时会发生什么?
Postgres使用TOAST(专用的影子表存储)。当且仅当选择行和列时,才会拉出大对象。换句话说,大量的黑盒子不会污染您宝贵的缓存。它还支持对TOASTed对象的压缩。
由于高端SSD存储供应商Fusion-io的贡献,MySQL具有称为“ 透明页面压缩”的更高级功能。它是专门为与SSD配合使用而设计的,固态硬盘的写入量与设备的寿命直接相关。
MySQL上的压缩不仅适用于页面外的大对象,而且适用于所有页面。它是通过在稀疏文件中使用打孔来实现的,稀疏文件受ext4或btrfs等现代文件系统支持。
有关更多详细信息,请参阅:通过FusionIO上的新MariaDB页面压缩显着提高性能
更新的开销
UPDATE
是经常遗漏但对性能有重大影响并且可能是最具争议的主题的另一个功能。
这也是Uber放弃Postgres
的另一个原因,这激起了许多Postgres拥护者的反驳。
- MySQL可能适合Uber,但不适合您
- PostgreSQL对Uber的回应(PDF)
两者都是MVCC数据库,可保留多个版本的数据以进行隔离。
为此,Postgres将旧数据保留在堆中直到VACUUMed,而MySQL将旧数据移动到称为回滚段的单独区域。
在Postgres上,当您尝试更新时,必须复制整行以及指向该行的索引条目。部分原因是Postgres不支持聚集索引,因此从索引引用的行的物理位置不会被逻辑键抽象出来。
为了解决此问题,Postgres使用仅堆元组(HOT)尽可能不更新索引。但是,如果更新足够频繁(或者如果一个元组很大),则元组的历史记录很容易从8KB的页面大小中流出,跨越多个页面并限制了功能的有效性。修剪和/或碎片整理的时间取决于试探法。此外,将fillfactor设置为小于100会降低空间效率—这是在表创建时就不必担心的艰难折衷。
这个限制甚至更深了。由于索引元组没有有关事务的任何信息,因此直到9.2 以前一直不可能支持仅索引扫描。它是所有主要数据库(包括MySQL,Oracle,IBM DB2和Microsoft SQL Server)支持的最古老,最重要的优化方法之一。但是即使使用最新版本,当有大量的UPDATE设置Visibility Map中的脏位时,Postgres也不能完全支持仅索引扫描,而在不需要时经常选择Seq扫描。
在MySQL上,更新发生在原地,旧行数据存放在称为回滚段的单独区域中。结果是您不需要VACUUM,提交非常快,而回滚相对较慢,这对于大多数用例来说是一个较好的折衷方案。
它也足够聪明,可以尽快清除历史记录。如果将事务的隔离级别设置为「READ-COMMITTED」或更低,则在语句完成时将清除历史记录。
交易历史记录的大小不会影响主页。碎片是没有问题的。因此,MySQL的整体性能更好,更可预测。
垃圾回收
Postgres上的VACUUM非常昂贵,因为它可以在主堆区域中工作,从而造成直接的资源征用。感觉就像编程语言中的垃圾回收一样-它会妨碍您并让您随意暂停。
为具有数十亿条记录的表配置自动清空仍然是一个挑战。
对MySQL的清除也可能很繁重,但是由于它在单独的回滚段中使用专用线程运行,因此不会以任何方式对读取并发产生不利影响。即使使用默认设置,膨胀的回滚段也不太可能使您减速。
一个拥有数十亿条记录的繁忙表不会导致MySQL的历史记录膨胀,并且诸如存储文件大小和查询性能之类的事情几乎是可以预测和稳定的。
日志和复制
Postgres有一个唯一的交易历史事实来源,称为Write Ahead Log(WAL)。它也用于复制,称为逻辑复制的新功能可以将二进制内容实时解码为更易消化的逻辑语句,从而可以对数据进行精细控制。
MySQL维护两个单独的日志:1. 用于崩溃恢复的InnoDB特定重做日志,以及2. 用于复制和增量备份的二进制日志。
与Oracle一样,InnoDB上的重做日志是免维护的循环缓冲区,不会随着时间的推移而增长,只能在启动时以固定大小创建。这种设计可确保在物理设备上保留连续的连续区域,从而提高性能。重做日志越大,性能越好,但要从崩溃中恢复时间。
在Postgres中添加了新的复制功能后,我称之为平局。
TL和DR
令人惊讶的是,事实证明,普遍的看法仍然成立。MySQL最适合在线交易,而PostgreSQL最适合仅追加分析过程,例如数据仓库。[2]
正如我们在本文中看到的,Postgres的绝大多数复杂性源于其仅附加的,过度冗余的堆体系结构。
Postgres的未来版本可能需要对其存储引擎进行重大改进。您不必完全相信我的话- 官方Wiki上已经讨论了它,这表明是时候从InnoDB那里获取一些好主意了。
一次又一次地说MySQL正在追赶Postgres,但是这次,潮流已经改变了。
- 顺便说一句,UUID作为主键是一个可怕的想法-密码随机性是完全「设计用来杀死」参考位置的,因此会降低性能。↩︎
- 当我说Postgres非常适合分析时,我是说真的。如果您不了解TimescaleDB,它是PostgreSQL之上的包装器,可让您每秒插入100万条记录,每服务器100 十亿行。疯狂的事情。难怪亚马逊为什么选择PostgreSQL作为Redshift的基础。