在技术社区中,"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,我们应当:

  1. 理解不同数据库的设计哲学和适用场景
  2. 根据业务需求选择合适的技术方案
  3. 避免"唯技术论"——最贵的不一定最适合,免费的也未必不够用

技术选型没有银弹,只有权衡。