在技术社区中,"Oracle vs MySQL"是一个经久不衰的话题。有人说MySQL分库分表后也能支撑几十亿数据,有人质疑Oracle高昂的授权费是否物有所值。本文将从查询优化器这一核心视角,通过实际测试数据,展示Oracle相比MySQL的技术优势。
本文源于V2EX社区的一个讨论,原帖提问:"MySQL分库分表能支撑几十亿数据,Oracle具体有什么优势?哪些场景是Oracle Only的?"
核心观点:优化器的差距
Oracle的CBO(Cost-Based Optimizer,基于成本的优化器)是目前所有关系型数据库中最复杂、最成熟的查询优化器。MySQL虽然在8.0版本后有了长足进步,但在优化器层面仍处于相对初级的阶段——这并非贬低MySQL,而是MySQL的设计目标本身并不追求实现极其复杂的优化器算法。
对于简单的OLTP查询(单表、走索引、返回少量行),两者差别不大。但当SQL变得复杂——多表关联、聚合计算、无索引全表扫描——Oracle优化器的优势就会显现出来。
实测对比:同一SQL,性能差距250倍以上
以下测试在同一台机器上进行,数据量完全相同(约92万行),且故意不使用任何索引,以便纯粹对比优化器和执行引擎的能力。
测试环境
| 项目 | Oracle | MySQL |
|---|---|---|
| 版本 | 11.2.0.4 Enterprise Edition | 8.0.14 |
| 数据表 | SH.SALES(Oracle示例Schema) | 相同结构和数据 |
| 数据量 | 918,843 行 | 918,843 行 |
| 索引 | 无(测试目的) | 无(测试目的) |
测试SQL
这是一个典型的多表关联聚合查询,涉及4张表的JOIN和GROUP BY:
SELECT c.cust_city,
t.calendar_quarter_desc,
SUM(s.amount_sold) sales_amount
FROM sh.sales s, sh.times t, sh.customers c, sh.channels ch
WHERE s.time_id = t.time_id
AND s.cust_id = c.cust_id
AND s.channel_id = ch.channel_id
AND c.cust_state_province = 'FL'
AND ch.channel_desc = 'Direct Sales'
AND t.calendar_quarter_desc IN ('2000-01', '2000-02','1999-12')
GROUP BY c.cust_city, t.calendar_quarter_desc;
测试结果
| 数据库 | 执行时间 | 返回行数 |
|---|---|---|
| Oracle 11g | 0.04 秒 | 24 行 |
| MySQL 8.0 | >10 分钟(未完成) | - |
结果令人震惊:Oracle在0.04秒内返回结果,而MySQL运行超过10分钟仍未完成。
为什么会有如此大的差距?
Oracle的执行计划分析
通过Oracle的执行计划,我们可以看到优化器做出了一系列精妙的决策:
------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 607 | 46132 | 955 (2)| 00:00:12 | | |
| 1 | HASH GROUP BY | | 607 | 46132 | 955 (2)| 00:00:12 | | |
|* 2 | HASH JOIN | | 2337 | 173K| 954 (2)| 00:00:12 | | |
| 3 | PART JOIN FILTER CREATE | :BF0000 | 274 | 4384 | 18 (0)| 00:00:01 | | |
|* 4 | TABLE ACCESS FULL | TIMES | 274 | 4384 | 18 (0)| 00:00:01 | | |
|* 5 | HASH JOIN | | 12456 | 729K| 936 (2)| 00:00:12 | | |
| 6 | MERGE JOIN CARTESIAN | | 383 | 14937 | 408 (1)| 00:00:05 | | |
|* 7 | TABLE ACCESS FULL | CHANNELS | 1 | 13 | 3 (0)| 00:00:01 | | |
| 8 | BUFFER SORT | | 383 | 9958 | 405 (1)| 00:00:05 | | |
|* 9 | TABLE ACCESS FULL | CUSTOMERS | 383 | 9958 | 405 (1)| 00:00:05 | | |
| 10 | PARTITION RANGE JOIN-FILTER| | 918K| 18M| 526 (2)| 00:00:07 |:BF0000|:BF0000|
| 11 | TABLE ACCESS FULL | SALES | 918K| 18M| 526 (2)| 00:00:07 |:BF0000|:BF0000|
------------------------------------------------------------------------------------------------------------
Oracle优化器的关键决策包括:
- Bloom Filter(:BF0000):在TIMES表上创建分区连接过滤器,提前过滤SALES表的无效分区
- 智能JOIN顺序:先处理小表(CHANNELS仅1行匹配、CUSTOMERS仅383行匹配),再与大表SALES关联
- Hash Join:对于无索引的大表关联,选择Hash Join而非低效的Nested Loop
- 分区裁剪:利用Partition Range Join-Filter,只扫描SALES表的相关分区
MySQL的执行计划分析
+----+-------------+-------+------+---------------+------+---------+------+--------+----------+----------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------+---------------+------+---------+------+--------+----------+----------------------------------------------------+
| 1 | SIMPLE | ch | ALL | NULL | NULL | NULL | NULL | 5 | 20.00 | Using where; Using temporary |
| 1 | SIMPLE | t | ALL | NULL | NULL | NULL | NULL | 1804 | 30.00 | Using where; Using join buffer (Block Nested Loop) |
| 1 | SIMPLE | c | ALL | NULL | NULL | NULL | NULL | 55065 | 10.00 | Using where; Using join buffer (Block Nested Loop) |
| 1 | SIMPLE | s | ALL | NULL | NULL | NULL | NULL | 914584 | 0.10 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------+---------------+------+---------+------+--------+----------+----------------------------------------------------+
MySQL的问题在于:
- Block Nested Loop:所有表都使用Block Nested Loop连接,这是一种相对低效的连接算法
- 全表扫描叠加:5 × 1804 × 55065 × 914584 ≈ 4.5×1017次比较(理论最坏情况)
- 缺乏高级优化:没有Bloom Filter、没有智能的JOIN重排序、没有分区裁剪
Oracle的其他核心优势
除了优化器,Oracle在以下方面也具有显著优势:
1. 单表支撑海量数据
Oracle单表可以轻松支撑几十亿甚至上百亿行数据,配合分区表(Partitioning)、并行查询(Parallel Query)等特性,无需像MySQL那样进行分库分表。这大大简化了应用架构,避免了分表键带来的跨分片查询难题。
2. 企业级高可用方案
- Data Guard:物理/逻辑备库,支持实时同步和延迟应用
- RAC:多实例共享存储的集群架构,实现负载均衡和故障切换
- GoldenGate:异构数据库间的实时数据同步
3. 完善的事务与并发控制
- 成熟的MVCC实现,读不阻塞写、写不阻塞读
- 丰富的锁机制和死锁检测
- Undo表空间管理,支持闪回查询(Flashback Query)
4. 强大的诊断与调优工具
- AWR/ASH:自动工作负载存储库,性能问题追溯利器
- SQL Tuning Advisor:自动SQL优化建议
- Real Application Testing:生产负载回放测试
那MySQL就不能用了吗?
当然不是。MySQL在以下场景仍然是优秀的选择:
- 互联网OLTP场景:简单查询、高并发读写、走索引的场景,MySQL表现优秀
- 成本敏感项目:开源免费,社区版足以满足大多数中小型应用
- 云原生架构:与Kubernetes、微服务架构天然契合
- 读写分离架构:主从复制成熟稳定,易于扩展读能力
关键在于场景匹配:
| 场景 | 推荐选择 |
|---|---|
| 简单OLTP、高并发短事务 | MySQL |
| 复杂报表、多表关联分析 | Oracle |
| 单表数据量超过10亿 | Oracle(或MySQL分库分表+中间件) |
| 金融、电信等强合规行业 | Oracle |
| 互联网创业公司 | MySQL / PostgreSQL |
| 混合负载(HTAP) | Oracle / TiDB |
写在最后
本文并非"拔高Oracle、贬低MySQL",而是希望通过实测数据展示两者的客观差异。Oracle数十年积累的优化器技术确实领先,这也是其高昂授权费的技术底气所在。
作为DBA,我们应当:
- 理解不同数据库的设计哲学和适用场景
- 根据业务需求选择合适的技术方案
- 避免"唯技术论"——最贵的不一定最适合,免费的也未必不够用
技术选型没有银弹,只有权衡。