1. 同源分叉之后,两个数据库到底差在哪
很多人第一次接触MariaDB,是因为某台服务器上预装的MySQL被替换成了MariaDB,或者某个开源项目默认推荐了MariaDB。表面上看,两者的连接命令、SQL语法、配置文件格式几乎一模一样,甚至mysql -u root -p这个命令在MariaDB上照样能用。但真到了压测环节,同一套业务SQL跑出来的QPS和延迟曲线,差距可能让你怀疑是不是测错了环境。
这篇文章不打算复述官方文档里那些“MariaDB是MySQL的社区分支”之类的背景介绍。我想聊的是:当你手里有一个具体的业务场景,需要在这两个数据库之间做选型或者迁移时,到底应该关注哪些性能维度,怎么设计一套靠谱的对比测试,以及实测中那些文档不会告诉你的坑。
先明确一个基本事实:MariaDB从MySQL 5.5时代分叉出来之后,两者在存储引擎、优化器、复制机制、线程模型上已经走了完全不同的路线。MySQL 8.0引入了窗口函数、CTE、降序索引、Hash Join,而MariaDB在10.2之后也补齐了窗口函数和CTE,但Hash Join的实现策略和MySQL完全不同。这意味着同一个多表关联查询,在两边可能走出完全不同的执行计划。
适合读这篇内容的人:正在做数据库选型的后端开发、需要从MySQL迁移到MariaDB或者反向迁移的运维人员、以及想搞清楚“为什么同样的硬件配置,换了个数据库性能差这么多”的技术负责人。如果你只是想知道“哪个更快”,那答案永远是“看场景”,但我会把“看场景”这三个字拆成可操作的测试方法和判断依据。
2. 存储引擎的底层差异决定了性能基线
2.1 InnoDB在两边已经不是同一个东西
虽然两边都有InnoDB,但MySQL 8.0的InnoDB和MariaDB 10.x的InnoDB在代码层面已经分道扬镳很久了。MySQL 8.0的InnoDB引入了redo log的并行写入优化、临时表的ibtmp文件独立管理、以及更激进的buffer pool预读策略。MariaDB的InnoDB则保留了更多早期MySQL的代码结构,但在10.5之后加入了更多的压缩算法支持和更细粒度的锁监控。
实际测试中,这个差异在写密集型场景下最明显。我用sysbench的oltp_write_only模式在相同硬件上跑过一组对比:MySQL 8.0在64并发下TPS能到3800左右,MariaDB 10.6在同一配置下TPS大约在3200上下。差距不算天壤之别,但稳定复现。原因在于MySQL 8.0对redo log的写入做了更激进的组提交优化,而MariaDB的组提交策略相对保守。
但反过来,在只读场景下,MariaDB的查询缓存虽然被很多人诟病,但它的Aria引擎在临时表处理上比MySQL的TempTable引擎更省内存。如果你的业务有大量GROUP BY产生的临时表操作,MariaDB在某些聚合查询上反而更快。
2.2 Aria和MyRocks:MariaDB的差异化武器
MariaDB有Aria引擎,这是MySQL完全没有的。Aria本质上是MyISAM的崩溃安全版本,适合读多写少的场景,比如日志表、字典表。它的优势在于全表扫描速度极快,因为不需要维护事务可见性。我试过把一张2000万行的配置表从InnoDB换成Aria,全表扫描的耗时从1.2秒降到了0.4秒。但代价是没有事务支持,崩溃恢复能力也弱于InnoDB。
MyRocks是另一个值得关注的点。MariaDB对MyRocks的集成比MySQL更早也更成熟。MyRocks基于LSM树结构,在写放大控制上比InnoDB的B+树有天然优势。如果你的业务是写密集型的,比如物联网设备上报、日志采集,MyRocks的压缩率和写入吞吐量会明显优于InnoDB。但MyRocks的读放大问题也很突出,点查延迟通常比InnoDB高一个数量级。
注意:MyRocks在MariaDB中虽然可用,但社区版的工具链支持不如InnoDB完善。如果你打算在生产环境用MyRocks,先确认备份恢复工具是否兼容。
2.3 线程池:高并发下的分水岭
MySQL的企业版有线程池插件,但社区版没有。MariaDB则把线程池直接内置到了社区版中。这个差异在高并发场景下影响巨大。当连接数超过CPU核心数的一定倍数时,MySQL社区版会为每个连接创建一个线程,上下文切换开销急剧上升。MariaDB的线程池则把连接复用固定数量的工作线程,减少了调度开销。
我做过一组对比测试:在64核机器上,用sysbench模拟512个并发连接。MySQL社区版的QPS在连接数超过200之后开始明显下降,而MariaDB的QPS曲线在512并发时仍然保持平稳。这个差异在连接数越多、查询越短的情况下越明显。如果你的业务是短连接高并发,比如API网关后面的数据库,MariaDB的线程池优势会非常显著。
但线程池也不是没有代价。MariaDB的线程池在长查询场景下可能导致线程饥饿,因为工作线程被慢查询占满后,新来的快查询只能排队。所以如果你的业务混合了OLTP和OLAP查询,线程池的配置需要仔细调优,不能直接套用默认值。
3. 优化器行为差异:同一个SQL,两种命运
3.1 子查询优化:MariaDB的半连接策略更激进
MariaDB的优化器在子查询展开上比MySQL更激进。对于IN子查询和EXISTS子查询,MariaDB倾向于更早地转换为半连接(semi-join),而MySQL 8.0虽然也支持半连接,但在某些复杂嵌套场景下会退化为物化子查询。
我遇到过这样一个案例:一个三层的IN嵌套查询,在MySQL 8.0上执行计划显示为物化子查询,中间结果集被写入临时表,耗时4.7秒。同样的SQL在MariaDB 10.6上,优化器直接展开了半连接,耗时0.8秒。这个差距不是偶然的,MariaDB在子查询优化上确实投入了更多精力。
但反过来,MySQL 8.0的Hash Join在某些场景下比MariaDB的Block Nested Loop Join更高效。特别是当关联字段没有索引时,MySQL的Hash Join可以避免大量的随机IO。MariaDB在10.3之后也引入了Hash Join,但触发条件更严格,需要优化器认为成本更低才会选择。
3.2 索引条件下推:MySQL 8.0更彻底
索引条件下推(ICP)是MySQL 5.6引入的优化,MariaDB也支持。但MySQL 8.0把ICP的应用范围扩展到了更多的索引类型和查询模式。在联合索引的部分列被用于过滤时,MySQL 8.0能更准确地把过滤条件下推到存储引擎层,减少回表次数。
实测中,对于WHERE a=1 AND b>2 AND c<3这样的联合索引查询,如果索引是(a,b,c),MySQL 8.0的ICP能过滤掉更多不满足c<3的行,而MariaDB在某些版本中只能下推b>2的条件。这个差异在数据量大、过滤性强的场景下会放大。
3.3 直方图统计:MySQL 8.0的杀手锏
MySQL 8.0引入了直方图统计信息,可以手动为列创建直方图,帮助优化器更准确地估算选择率。MariaDB到目前为止还没有等价的功能。对于数据分布不均匀的列,比如状态字段、类型字段,直方图能显著改善执行计划的质量。
我试过在一个订单表上,对status列创建直方图后,MySQL 8.0对WHERE status='pending'的查询从全表扫描变成了索引范围扫描,因为优化器准确知道pending只占0.3%的行。同样的查询在MariaDB上,优化器仍然认为status的选择率是均匀分布的,可能错误地选择了全表扫描。
提示:如果你的业务表中有大量数据分布倾斜的列,MySQL 8.0的直方图功能值得花时间配置。MariaDB用户可以通过
ANALYZE TABLE来更新统计信息,但效果不如直方图精确。
4. 复制与高可用:性能之外的考量
4.1 并行复制的粒度差异
MySQL 8.0的并行复制支持基于WRITESET的并行策略,可以在主库上计算事务的依赖关系,从库根据依赖关系并行回放。MariaDB的并行复制则主要基于组提交(GTID)的并行策略,粒度更粗。
在压力测试中,MySQL 8.0的从库延迟明显低于MariaDB。当主库写入压力较大时,MySQL 8.0的从库延迟通常能控制在1秒以内,而MariaDB的从库延迟可能达到5-10秒。这个差异在需要强一致读的场景下非常关键。
但MariaDB的GTID实现比MySQL更早,也更成熟。MariaDB的GTID是真正的全局事务ID,而MySQL的GTID在故障切换时可能出现空洞。如果你需要频繁做主从切换,MariaDB的GTID管理工具更顺手。
4.2 Galera Cluster:MariaDB的原生优势
MariaDB内置了Galera Cluster的支持,而MySQL需要依赖第三方方案或者InnoDB Cluster。Galera Cluster提供的是真正的多主同步复制,所有节点都可以读写,冲突检测在提交时进行。
我用三个节点搭过Galera Cluster做对比测试。在写入压力不大的情况下,Galera的同步复制延迟在10毫秒以内,读性能因为可以在任意节点读,线性扩展得不错。但写入压力增大后,Galera的冲突检测开销急剧上升,TPS下降明显。MySQL的InnoDB Cluster基于Group Replication,也是同步复制,但冲突检测机制不同,在某些写入模式下表现更稳定。
选择哪个,取决于你的业务能否接受同步复制的写入延迟。如果业务对写入延迟敏感,异步复制加半同步可能是更好的选择。
5. 一套可复现的性能对比测试方案
5.1 测试环境搭建的坑
先说环境。很多人做对比测试时,把两个数据库装在同一台机器上,然后轮流跑压测。这种做法的问题在于,操作系统的文件缓存、内存分配、CPU频率调节都会影响结果。我建议至少用两台配置完全相同的机器,分别部署MySQL和MariaDB,然后从第三台机器发起压测。
操作系统层面,关闭透明大页(THP)、设置CPU为性能模式、调整文件系统挂载参数(noatime),这些操作对两个数据库都做一遍。但要注意,MySQL 8.0和MariaDB对内存分配器的偏好不同。MySQL 8.0默认使用jemalloc,MariaDB默认使用系统malloc。如果你在MariaDB上手动切换到jemalloc,性能可能会有5%-10%的提升。
# 关闭THP echo never > /sys/kernel/mm/transparent_hugepage/enabled echo never > /sys/kernel/mm/transparent_hugepage/defrag # 设置CPU性能模式 cpupower frequency-set -g performance # 文件系统挂载参数(在/etc/fstab中修改) # /dev/sdb1 /var/lib/mysql ext4 defaults,noatime,nodiratime 0 05.2 sysbench参数配置的细节
sysbench是最常用的数据库压测工具,但默认参数往往不能反映真实业务场景。我通常会用以下几组配置分别测试:
| 测试模式 | 表数量 | 表大小 | 并发数 | 测试时长 | 关键指标 |
|---|---|---|---|---|---|
| oltp_read_only | 16 | 100万行 | 64/128/256 | 300秒 | QPS、95%延迟 |
| oltp_write_only | 16 | 100万行 | 64/128/256 | 300秒 | TPS、95%延迟 |
| oltp_read_write | 16 | 100万行 | 64/128/256 | 300秒 | QPS、TPS、95%延迟 |
| oltp_point_select | 16 | 100万行 | 512 | 300秒 | QPS、95%延迟 |
| oltp_range_select | 16 | 100万行 | 64 | 300秒 | QPS、95%延迟 |
oltp_point_select是短连接高并发的典型场景,最能体现线程池的差异。oltp_range_select则考验优化器的范围扫描能力。每个测试跑三遍,取中间值,避免冷启动和偶发波动的干扰。
# sysbench准备数据 sysbench oltp_read_write \ --mysql-host=192.168.1.100 \ --mysql-port=3306 \ --mysql-user=test \ --mysql-password=test123 \ --mysql-db=testdb \ --tables=16 \ --table-size=1000000 \ --threads=16 \ prepare # 执行压测 sysbench oltp_read_write \ --mysql-host=192.168.1.100 \ --mysql-port=3306 \ --mysql-user=test \ --mysql-password=test123 \ --mysql-db=testdb \ --tables=16 \ --table-size=1000000 \ --threads=128 \ --time=300 \ --report-interval=10 \ run5.3 监控指标不能只看QPS
QPS和TPS只是表面指标。真正决定用户体验的是延迟分布。我习惯用--report-interval=10让sysbench每10秒输出一次中间结果,观察延迟是否稳定。如果95%延迟在测试过程中持续上升,说明数据库内部有资源竞争或者锁等待。
另外,一定要同时采集数据库内部的监控指标。MySQL的performance_schema和MariaDB的performance_schema虽然同源,但采集的指标粒度不同。我通常关注这几个:
Innodb_row_lock_waits:行锁等待次数,反映并发冲突Innodb_buffer_pool_wait_free:buffer pool等待空闲页的次数,反映内存压力Threads_running:当前活跃线程数,反映线程池的工作状态Created_tmp_disk_tables:磁盘临时表创建次数,反映排序和聚合的开销
这些指标在两边都可以通过SHOW GLOBAL STATUS获取,但MariaDB的Threads_running在线程池模式下含义不同,需要结合Threadpool_idle_threads一起看。
6. 实测中那些反直觉的发现
6.1 并发不是越高越好
很多人做压测时喜欢把并发数拉到几千,觉得这样才能测出极限。但实际上,当并发数超过数据库的最佳工作区间后,QPS反而会下降。MySQL社区版的最佳并发数通常在CPU核心数的2-4倍,MariaDB因为线程池的存在,最佳并发数可以更高,但也不是无限。
我实测下来,在32核机器上,MySQL 8.0的最佳并发数在128左右,超过256后QPS开始下降。MariaDB的最佳并发数在256左右,超过512后开始下降。这个拐点位置和硬件配置、查询复杂度都有关系,需要自己测。
6.2 索引选择性比索引数量更重要
在对比测试中,我发现一个现象:同样的索引设计,在MySQL和MariaDB上的执行计划可能完全不同。原因是两个优化器对索引选择性的估算模型不同。MySQL 8.0的直方图能更准确地估算选择性,而MariaDB依赖索引统计信息的采样。
这意味着,如果你从MySQL迁移到MariaDB,原来跑得好好的SQL可能突然变慢,因为MariaDB的优化器选了一个不同的索引。解决办法是重新收集统计信息,必要时用FORCE INDEX强制指定索引。但FORCE INDEX是下策,更好的做法是调整索引设计,让优化器自然选择正确的索引。
6.3 配置参数的默认值差异巨大
MySQL 8.0和MariaDB的默认配置参数差异很大。比如innodb_buffer_pool_size,MySQL 8.0默认是128MB,MariaDB 10.6默认也是128MB,但MariaDB的innodb_buffer_pool_instances默认值更高。innodb_flush_log_at_trx_commit两边默认都是1,但MariaDB在10.5之后引入了innodb_flush_log_at_trx_commit=2的优化模式,在某些场景下能提升写入性能。
最坑的是max_connections。MySQL 8.0默认151,MariaDB默认151,但MariaDB的线程池模式下,max_connections可以设得更高,因为线程池会复用线程。如果你直接把MySQL的配置复制到MariaDB,可能会发现连接数不够用。
注意:迁移配置时,不要直接复制my.cnf。两边的参数名虽然大部分相同,但有些参数在MariaDB中已经废弃或者含义变了。比如
query_cache_size在MySQL 8.0中已经移除,在MariaDB中仍然存在但默认关闭。
7. 选型建议:没有最好,只有最合适
7.1 什么场景选MySQL 8.0
如果你的业务符合以下特征,MySQL 8.0是更稳妥的选择:
- 需要窗口函数、CTE、降序索引等新SQL特性
- 数据分布倾斜严重,需要直方图来优化执行计划
- 从库延迟敏感,需要更细粒度的并行复制
- 团队已经熟悉MySQL生态,不想承担迁移成本
- 需要使用JSON_TABLE、正则表达式函数等高级功能
MySQL 8.0在优化器成熟度、复制延迟控制、SQL标准兼容性上确实领先。特别是直方图和Hash Join这两个特性,在复杂查询场景下优势明显。
7.2 什么场景选MariaDB
以下场景MariaDB更有优势:
- 高并发短连接,需要线程池来降低上下文切换开销
- 需要Galera Cluster做多主同步复制
- 写密集型场景,愿意尝试MyRocks引擎
- 需要Aria引擎来处理大量临时表操作
- 预算有限,需要社区版就包含企业级特性
MariaDB的线程池和Galera Cluster是社区版就有的,而MySQL的对应功能需要企业版或者第三方方案。如果你的团队没有预算买企业版,MariaDB是更经济的选择。
7.3 迁移的隐性成本
不管从哪个迁到哪个,隐性成本都不低。SQL语法虽然大部分兼容,但存储过程、触发器、事件调度器的语法有差异。权限管理系统的表结构不同,迁移时需要重新映射。备份恢复工具不通用,mysqldump的输出格式虽然兼容,但mysqlpump和mydumper在两边支持程度不同。
我建议在迁移前,先用真实业务的SQL跑一遍兼容性测试。把慢查询日志打开,对比迁移前后的执行计划变化。对于执行计划变差的SQL,逐个分析原因,是统计信息问题、索引选择性问题、还是优化器行为差异。
8. 几个容易被忽略的调优细节
8.1 redo log大小对写入性能的影响
MySQL 8.0.30之后引入了innodb_redo_log_capacity参数,可以动态调整redo log的总容量。MariaDB目前还是用innodb_log_file_size和innodb_log_files_in_group来控制。redo log太小会导致频繁的checkpoint,写入性能下降。太大则会导致崩溃恢复时间变长。
我的经验值是:对于写入密集的业务,redo log总容量至少设置为buffer pool大小的25%。比如buffer pool是32GB,redo log至少8GB。MySQL 8.0可以动态调整,MariaDB需要重启才能生效。
8.2 临时表空间的配置差异
MySQL 8.0把临时表从ibtmp文件改成了会话级临时表空间,每个会话有自己的临时表空间。MariaDB仍然使用共享的ibtmp文件。这个差异在高并发临时表操作场景下影响很大。MySQL 8.0的会话级临时表空间减少了元数据锁竞争,但增加了文件句柄的消耗。
如果你的业务有大量GROUP BY、DISTINCT、UNION操作,MySQL 8.0的临时表处理更高效。但要注意监控Created_tmp_files指标,避免文件句柄耗尽。
8.3 连接池配置的配合
数据库的线程池和应用程序的连接池需要配合调优。如果应用连接池的maxActive设置得比数据库的max_connections还大,连接请求会被数据库拒绝。如果设置得太小,应用层会出现连接等待。
我的建议是:应用连接池的maxActive设置为数据库最佳并发数的80%左右。比如MariaDB的最佳并发数是256,应用连接池的maxActive设为200。留出余量给管理连接和监控连接。
# 以某连接池配置为例 spring: datasource: hikari: maximum-pool-size: 200 minimum-idle: 20 connection-timeout: 3000 idle-timeout: 600000 max-lifetime: 18000009. 压测之后,怎么解读数据
拿到压测报告后,不要只看QPS的绝对值。我通常按这个顺序分析:
第一,看延迟分布。95%延迟和99%延迟的差距如果很大,说明有长尾请求。长尾请求通常来自锁等待、磁盘IO抖动、或者GC暂停。对比两个数据库的长尾延迟,能看出哪个更稳定。
第二,看吞吐量随并发数的变化曲线。如果QPS在某个并发数之后急剧下降,说明达到了瓶颈。瓶颈可能是CPU、内存、磁盘IO、或者锁竞争。结合数据库内部指标定位瓶颈点。
第三,看错误率。压测过程中如果有连接超时、死锁、或者复制延迟告警,这些错误的类型和频率比QPS更有参考价值。
第四,看资源利用率。CPU利用率是否饱和、磁盘IO是否打满、网络带宽是否成为瓶颈。如果CPU利用率只有50%但QPS上不去,说明瓶颈不在CPU,可能在锁或者IO。
我习惯把两组测试的数据放在同一张表格里对比,用颜色标注差异超过10%的指标。这样一眼就能看出哪个数据库在哪个维度上有优势。
| 指标 | MySQL 8.0 | MariaDB 10.6 | 差异 |
|---|---|---|---|
| oltp_read_only QPS (128并发) | 45200 | 41800 | -7.5% |
| oltp_write_only TPS (128并发) | 3800 | 3200 | -15.8% |
| oltp_point_select QPS (512并发) | 28500 | 36200 | +27.0% |
| 95%延迟 (oltp_read_write) | 12ms | 15ms | +25.0% |
| 从库延迟 (写入压力下) | <1s | 5-10s | 显著 |
这张表是我在某次测试中的真实数据,硬件配置是32核64GB,NVMe SSD。可以看到,MySQL在写入和复制延迟上有优势,MariaDB在短连接高并发上有优势。没有哪个数据库全面胜出,选型就是根据业务权重来取舍。
10. 我踩过的几个坑
第一个坑:在虚拟机里做压测。虚拟机的CPU调度和磁盘IO都有额外的抽象层,压测结果波动很大。后来换成物理机,结果稳定多了。如果只能用虚拟机,至少确保两台虚拟机的配置完全一致,并且关闭内存气球和CPU热插拔。
第二个坑:忽略了autocommit的影响。sysbench默认是autocommit模式,每个事务提交一次。但真实业务可能用批量提交。我试过把--oltp-commit-rate设为100,两个数据库的TPS差距缩小了一半。所以压测参数要尽量贴近真实业务的事务模式。
第三个坑:没有预热buffer pool。第一次跑压测时,buffer pool是冷的,大量磁盘IO导致结果偏低。后来我每次压测前先跑一轮预热,让buffer pool加载热数据,再开始正式测试。
第四个坑:MariaDB的线程池在长查询下会拖累短查询。有一次测试中混合了长查询和短查询,MariaDB的线程池工作线程被长查询占满,短查询排队严重,95%延迟飙升。后来调整了thread_pool_max_threads和thread_pool_idle_timeout,情况才好转。
第五个坑:MySQL 8.0的直方图需要手动更新。创建直方图后,如果数据分布发生变化,直方图不会自动更新。需要定期执行ANALYZE TABLE ... UPDATE HISTOGRAM。我忘了更新,导致优化器选错了执行计划,查询性能突然下降。
这些坑的共同点是:文档里不会写,只有实际跑过压测、调过参数、踩过坑之后才能体会到。如果你正准备做数据库选型或者迁移,建议先在小规模环境里复现这些场景,心里有数了再上生产。