适用场景:电商平台、网络游戏、高并发互联网系统
数据规模:百万级、千万级、亿级、百亿级
数据库:MySQL 8.0 / 8.4、Oracle 19c / 23ai
核心目标:提高 SQL 执行效率、提升并发能力、降低资源消耗、保证数据一致性与稳定性。
| 数据规模 | 电商场景 | 网游场景 | 主要问题 |
|---|---|---|---|
| 10万 | 小型商城订单 | 游戏角色数据 | 单库通常可以支撑 |
| 100万 | 商品、用户、订单 | 角色、装备、背包 | SQL 和索引设计 |
| 1000万 | 订单明细 | 玩家交易流水 | 慢查询、索引膨胀 |
| 1亿 | 订单、支付流水 | 游戏行为日志 | I/O、锁竞争、归档 |
| 10亿 | 多年历史订单 | 多区服交易日志 | 分区、冷热分离 |
| 100亿以上 | 大型电商平台 | 大型游戏集群 | 分布式架构、数据分层 |
注意:
数据量大不一定代表性能差。
数据库性能还取决于:
例如:
数据库调优不能一开始就增加索引、扩大内存或者增加服务器。
正确思路:
发现性能问题
↓
收集性能指标
↓
确定瓶颈位置
↓
分析SQL执行计划
↓
制定优化方案
↓
测试环境验证
↓
灰度上线
↓
监控优化效果
核心原则:
先定位问题,再实施优化;先优化 SQL 和索引,再考虑数据库架构扩展。
| 层次 | 优化内容 | 常见方案 |
|---|---|---|
| 第一层 | SQL | SQL 改写、减少扫描 |
| 第二层 | 索引 | 联合索引、覆盖索引 |
| 第三层 | 表结构 | 分区、归档、冷热分离 |
| 第四层 | 数据库实例 | 内存、Redo、连接池 |
| 第五层 | 系统架构 | Redis、读写分离、分库分表 |
| 第六层 | 硬件 | CPU、内存、SSD、网络 |
常见优化顺序:
SQL优化
↓
索引优化
↓
事务与锁优化
↓
表结构优化
↓
数据库参数优化
↓
缓存与读写分离
↓
分区、分库分表
↓
硬件扩容
注意:实际优化顺序应由监控结果决定。
SHOW GLOBAL STATUS LIKE 'Questions';
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW GLOBAL STATUS LIKE 'Threads_running';
SHOW GLOBAL STATUS LIKE 'Slow_queries';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';
SHOW GLOBAL STATUS LIKE 'Innodb_row_lock%';
重点指标:
| 指标 | 作用 |
|---|---|
| Threads_connected | 当前连接数 |
| Threads_running | 活跃线程数 |
| Slow_queries | 累计慢查询数量 |
| Innodb_buffer_pool_reads | 物理读取次数 |
| Innodb_row_lock_waits | 行锁等待次数 |
注意:QPS、TPS 应通过两个采样时间点的计数器差值计算。
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
说明:
long_query_time=1 表示慢查询阈值为1秒。SET GLOBAL 对已建立连接的会话级参数不一定立即生效。SET PERSIST。分析慢查询:
mysqldumpslow -s t -t 20 slow.log
也可以使用:
pt-query-digest slow.log
重点分析:
SHOW FULL PROCESSLIST;
重点关注:
MySQL 8.0 可以使用 Performance Schema:
SELECT
DIGEST_TEXT,
COUNT_STAR,
ROUND(AVG_TIMER_WAIT / 1e12, 3) AS avg_seconds,
ROUND(SUM_TIMER_WAIT / 1e12, 3) AS total_seconds,
SUM_ROWS_EXAMINED,
SUM_ROWS_SENT
FROM performance_schema.events_statements_summary_by_digest
WHERE DIGEST_TEXT IS NOT NULL
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 20;
作用:
找出累计消耗数据库时间最多的 SQL。
假设订单表有1亿条数据。
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
order_no VARCHAR(64) NOT NULL,
status TINYINT NOT NULL,
amount DECIMAL(12,2) NOT NULL,
create_time DATETIME NOT NULL
) ENGINE=InnoDB;
原始 SQL:
SELECT *
FROM orders
WHERE user_id = 10001
ORDER BY create_time DESC
LIMIT 20;
问题:
SELECT * 增加数据读取与传输。优化:
CREATE INDEX idx_user_time_id
ON orders(user_id, create_time DESC, id DESC);
改写 SQL:
SELECT
id,
order_no,
status,
amount,
create_time
FROM orders
WHERE user_id = 10001
ORDER BY create_time DESC, id DESC
LIMIT 20;
优化原理:
user_id 定位订单。id 保证稳定排序。验证:
EXPLAIN ANALYZE
SELECT
id,
order_no,
status,
amount,
create_time
FROM orders
WHERE user_id = 10001
ORDER BY create_time DESC, id DESC
LIMIT 20;
重点观察:
原始 SQL:
SELECT *
FROM orders
ORDER BY id
LIMIT 1000000, 20;
问题:
MySQL 可能需要扫描大量记录,再丢弃前面的数据。
优化:
SELECT *
FROM orders
WHERE id > 1000000
ORDER BY id
LIMIT 20;
注意:
这里的 1000000 必须是上一页最后一条记录的真实 ID,而不是分页偏移量。
这种方式称为:
游标分页 / Keyset Pagination
适用场景:
缺点:
不适合直接随机跳转到任意页。
原始 SQL:
SELECT *
FROM orders
WHERE DATE(create_time) = '2026-10-01';
问题:
对索引字段使用函数,可能导致普通索引无法被高效使用。
优化:
SELECT *
FROM orders
WHERE create_time >= '2026-10-01'
AND create_time < '2026-10-02';
使用时间范围查询,通常更容易利用 B-Tree 索引。
原始 SQL:
SELECT *
FROM player_inventory
WHERE player_id = 10001;
优化:
SELECT item_id, quantity
FROM player_inventory
WHERE player_id = 10001;
收益:
游戏装备表:
CREATE TABLE player_equipment (
id BIGINT PRIMARY KEY,
player_id BIGINT NOT NULL,
equipment_type INT NOT NULL,
quality INT NOT NULL,
level INT NOT NULL
);
查询:
SELECT *
FROM player_equipment
WHERE player_id = 10001
AND equipment_type = 2;
优化:
CREATE INDEX idx_player_type
ON player_equipment(player_id, equipment_type);
设计联合索引时需要考虑:
不要机械地认为选择性最高的字段必须排在第一位。
CREATE INDEX idx_player_type_quality
ON player_equipment(
player_id,
equipment_type,
quality
);
查询:
SELECT quality
FROM player_equipment
WHERE player_id = 10001
AND equipment_type = 2;
可能直接从索引获取结果,减少回表操作。
适用场景:
索引不是越多越好。
索引过多会导致:
优化建议:
[mysqld]
innodb_buffer_pool_size=16G
作用:
缓存 InnoDB 数据页和索引页。
对于独占数据库服务器,常见起点为物理内存的50%~75%,部分环境可以更高。
例如:
32GB 内存的独占数据库服务器,可以先评估16GB~24GB范围。
实际配置需要预留:
MySQL 8.0.30 及后续版本:
innodb_redo_log_capacity=4G
Redo Log 容量过小:
Redo Log 容量过大:
需要结合 Redo 生成速率、Checkpoint 压力和恢复时间要求调整。
innodb_flush_log_at_trx_commit=1
| 参数值 | 特点 | 风险 |
|---|---|---|
| 1 | 每次提交写入并刷盘 | 持久性保障较强 |
| 2 | 提交写入日志,通常周期性刷盘 | 系统崩溃可能丢失近期事务 |
| 0 | 通常周期性写入与刷盘 | 进程崩溃也可能丢失近期事务 |
对于:
通常优先使用:
innodb_flush_log_at_trx_commit=1
sync_binlog=1
还需要结合存储设备、复制和故障恢复机制保证整体可靠性。
适用场景:
示例:
CREATE TABLE game_log (
id BIGINT NOT NULL,
player_id BIGINT NOT NULL,
event_type INT NOT NULL,
create_time DATETIME NOT NULL,
PRIMARY KEY(id, create_time)
)
PARTITION BY RANGE COLUMNS(create_time) (
PARTITION p202601 VALUES LESS THAN ('2026-02-01'),
PARTITION p202602 VALUES LESS THAN ('2026-03-01'),
PARTITION pmax VALUES LESS THAN (MAXVALUE)
);
注意:
MySQL 分区表的所有唯一键必须包含分区表达式涉及的列。
因此主键设计为:
PRIMARY KEY(id, create_time)
按时间查询:
SELECT *
FROM game_log
WHERE create_time >= '2026-02-01'
AND create_time < '2026-03-01';
可以利用分区裁剪。
分区优点:
注意:
分区不等于分库分表,也不意味着所有 SQL 都会变快。
生产环境需要定期维护分区,避免所有新数据长期进入 pmax。
当单实例吞吐量或存储容量成为明确瓶颈时,可以考虑水平拆分。
订单系统
│
├── order_db_0
│ ├── orders_00
│ ├── orders_01
│ └── orders_02
│
├── order_db_1
│ ├── orders_00
│ ├── orders_01
│ └── orders_02
│
└── order_db_2
├── orders_00
├── orders_01
└── orders_02
简单路由示例:
int dbIndex = (int) (userId % 4);
int tableIndex = (int) (userId % 16);
注意:
简单取模在扩容时会产生大量数据重新映射的问题。
生产环境需要解决:
常见方案:
SELECT
sid,
serial#,
username,
status,
sql_id,
event,
wait_class,
blocking_session
FROM v$session
WHERE status = 'ACTIVE'
AND type = 'USER';
重点关注:
SELECT *
FROM (
SELECT
sql_id,
executions,
elapsed_time / 1000000 AS total_seconds,
cpu_time / 1000000 AS cpu_seconds,
buffer_gets,
disk_reads
FROM v$sql
WHERE executions > 0
ORDER BY elapsed_time DESC
)
WHERE ROWNUM <= 20;
重点分析:
| 工具 | 作用 |
|---|---|
| AWR | 历史性能分析 |
| ASH | 活跃会话分析 |
| ADDM | 自动性能诊断 |
| SQL Monitor | SQL 执行过程监控 |
| DBMS_XPLAN | 执行计划分析 |
| Statspack | 历史性能统计 |
注意:
AWR、ASH、ADDM 及部分 SQL Monitor 功能涉及 Oracle 额外授权要求,生产环境应核对许可证。
EXPLAIN PLAN FOR
SELECT *
FROM orders
WHERE user_id = 10001;
查看执行计划:
SELECT *
FROM TABLE(DBMS_XPLAN.DISPLAY);
查看真实游标执行计划:
SELECT *
FROM TABLE(
DBMS_XPLAN.DISPLAY_CURSOR(
NULL,
NULL,
'ALLSTATS LAST'
)
);
注意:
ALLSTATS LAST 需要相关执行统计已被采集。
重点指标:
| 指标 | 含义 |
|---|---|
| E-Rows | 预计行数 |
| A-Rows | 实际行数 |
| Buffers | 逻辑读取 |
| Reads | 物理读取 |
| Starts | 执行次数 |
| Operation | 执行操作 |
如果:
E-Rows = 100
A-Rows = 1000000
说明优化器行数估算可能严重偏离实际情况。
需要检查:
原始 SQL:
SELECT *
FROM orders
WHERE user_id = 10001
ORDER BY create_time DESC;
建立索引:
CREATE INDEX idx_orders_user_time
ON orders(user_id, create_time DESC);
优化 SQL:
SELECT
id,
order_no,
amount,
create_time
FROM orders
WHERE user_id = 10001
ORDER BY create_time DESC
FETCH FIRST 20 ROWS ONLY;
优化收益:
Oracle 常见索引:
| 索引类型 | 适用场景 |
|---|---|
| B-Tree | OLTP、高选择性查询 |
| Bitmap | 数据仓库、读多写少 |
| Function-Based | 函数表达式查询 |
| Composite | 多字段查询 |
| Local Partitioned | 分区表本地索引 |
| Global Partitioned | 跨分区查询及特定唯一性需求 |
适合:
适合:
不推荐在频繁更新的电商订单表或游戏交易表上随意使用 Bitmap 索引。
原因:
可能产生严重的并发更新争用。
Oracle 优化器依赖统计信息选择执行计划。
收集统计信息:
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(
ownname => 'ECOMMERCE',
tabname => 'ORDERS',
cascade => TRUE
);
END;
/
统计信息包括:
典型问题:
数据量从100万增长到1亿
↓
统计信息没有及时更新
↓
优化器错误估算数据量
↓
选择不合理的执行计划
↓
SQL执行速度下降
优化建议:
示例:
CREATE TABLE orders (
id NUMBER NOT NULL,
user_id NUMBER NOT NULL,
amount NUMBER(12,2),
create_time DATE NOT NULL
)
PARTITION BY RANGE(create_time)
INTERVAL (NUMTOYMINTERVAL(1, 'MONTH'))
(
PARTITION p_initial
VALUES LESS THAN (DATE '2026-01-01')
);
特点:
查询:
SELECT *
FROM orders
WHERE create_time >= DATE '2026-10-01'
AND create_time < DATE '2026-11-01';
可以利用分区裁剪。
Oracle 内存结构:
Oracle Instance
│
├── SGA
│ ├── Database Buffer Cache
│ ├── Shared Pool
│ ├── Redo Log Buffer
│ └── 其他共享内存
│
└── PGA
├── 排序工作区
├── Hash Join工作区
└── 会话私有内存
查看 SGA:
SHOW PARAMETER sga_target;
SHOW PARAMETER sga_max_size;
查看 PGA:
SHOW PARAMETER pga_aggregate_target;
SHOW PARAMETER pga_aggregate_limit;
优化重点:
注意:
不能仅通过缓存命中率判断数据库性能。
Redo 主要用于事务恢复。
高并发交易中:
玩家交易
↓
修改金币
↓
修改装备
↓
生成Redo
↓
提交事务
常见等待事件:
log file sync
排查方向:
Undo 用于:
常见错误:
ORA-01555: snapshot too old
可能原因:
解决方向:
假设:
库存:100件
并发请求:100000
原始方案:
SELECT stock
FROM product_stock
WHERE product_id = 10001;
应用判断库存后:
UPDATE product_stock
SET stock = stock - 1
WHERE product_id = 10001;
问题:
多个请求可能同时读取相同库存值,产生并发逻辑错误。
优化:
UPDATE product_stock
SET stock = stock - 1
WHERE product_id = 10001
AND stock > 0;
通过受影响行数判断扣减是否成功。
但大量请求仍然可能竞争同一库存行。
更高并发场景:
用户请求
↓
限流与防刷
↓
Redis库存预检查
↓
消息队列削峰
↓
订单消费者
↓
数据库条件扣减
↓
订单状态确认
需要注意:
假设:
订单总量:20亿
最近3个月:2亿
历史订单:18亿
可以设计:
订单查询入口
│
├── 热数据订单库
│ └── 最近订单
│
└── 历史订单库
└── 历史订单
优点:
需要解决:
应用服务
│
数据访问层
│
┌────────┴────────┐
│ │
主库 从库
写入 查询
│ │
└────数据复制─────┘
适合:
需要注意:
主从复制可能存在延迟。
因此:
这些场景应考虑主库读取或其他一致性保障机制。
常见数据表:
player
player_attribute
player_inventory
player_equipment
player_currency
player_quest
player_mail
player_transaction_log
金币表:
CREATE TABLE player_currency (
player_id BIGINT PRIMARY KEY,
gold BIGINT NOT NULL DEFAULT 0,
diamond BIGINT NOT NULL DEFAULT 0,
version BIGINT NOT NULL DEFAULT 0
);
扣除金币:
UPDATE player_currency
SET gold = gold - 100,
version = version + 1
WHERE player_id = 10001
AND gold >= 100;
通过受影响行数判断是否成功。
如果涉及多个角色、装备和金币,需要保证整个交易流程的数据一致性。
大型 MMORPG 不适合把所有角色状态变化都立即写入数据库。
可以采用:
玩家登录
↓
加载角色数据
↓
游戏服务器内存维护状态
↓
战斗、升级、移动
↓
记录重要变更事件
↓
周期性保存检查点
↓
玩家下线持久化
但不能只依赖周期性保存。
否则服务器异常宕机可能造成数据丢失。
建议:
| 数据类型 | 持久化方式 |
|---|---|
| 角色坐标 | 周期性保存 |
| 临时战斗状态 | 内存维护 |
| 角色等级 | 关键事件持久化 |
| 金币、钻石 | 可靠事务或持久化事件 |
| 稀有装备 | 强一致持久化 |
| 玩家交易 | 事务、流水、幂等 |
| 战斗日志 | 异步写入 |
| 排行榜 | Redis 或专用存储 |
游戏网关
│
├── 1区
│ └── game_db_01
│
├── 2区
│ └── game_db_02
│
├── 3区
│ └── game_db_03
│
└── 跨服服务
└── global_db
优点:
缺点:
假设:
在线玩家:50万人
每人每分钟产生10条日志
计算:
每分钟:500万条
每秒平均:约8.3万条
每天:72亿条
不建议把所有日志同步写入核心交易数据库。
推荐架构:
游戏服务器
↓
日志采集
↓
Kafka
↓
流式处理
↓
分析型数据库 / 对象存储
↓
运营分析、反作弊、报表
可评估:
MySQL / Oracle 主要负责关键业务事务数据。
| 调优维度 | MySQL | Oracle |
|---|---|---|
| SQL 分析 | EXPLAIN ANALYZE | DBMS_XPLAN |
| 慢 SQL 定位 | Slow Query Log | V$SQL、ASH |
| SQL 统计 | Performance Schema | V$SQL、AWR |
| 统计信息 | ANALYZE TABLE、自动统计 | DBMS_STATS |
| 内存优化 | Buffer Pool | SGA、PGA |
| 事务日志 | Redo、Undo、Binlog | Redo、Undo、Archive Log |
| 索引 | B-Tree 为主 | B-Tree、Bitmap、函数索引 |
| 分区 | RANGE、LIST、HASH | RANGE、LIST、HASH、INTERVAL |
| 读写扩展 | 主从复制、读写分离 | Data Guard、Active Data Guard |
| 高可用 | InnoDB Cluster 等 | RAC、Data Guard |
| 水平扩展 | 分库分表、Vitess 等 | Oracle Sharding 等 |
| 执行计划管理 | 优化器控制与版本相关能力 | SQL Plan Management |
注意:
Oracle RAC 不等于分库分表。
RAC 主要用于多实例共享数据库的高可用和部分负载扩展。
| 问题 | 原因 | 解决方案 |
|---|---|---|
| SQL 突然变慢 | 执行计划变化 | 检查统计信息和执行计划 |
| 索引不生效 | 选择性低、条件不匹配 | 分析执行计划 |
| CPU 100% | 高频 SQL、复杂计算 | SQL 优化、降低无效调用 |
| I/O 很高 | 大量扫描、缓存不足 | 索引优化、检查存储 |
| 连接数耗尽 | 连接泄漏、连接池不合理 | 连接池治理 |
| 锁等待严重 | 长事务、热点行 | 缩短事务、降低竞争 |
| 死锁频繁 | 加锁顺序不一致 | 统一加锁顺序、幂等重试 |
| INSERT 变慢 | 索引过多、存储压力 | 索引治理、批量写入 |
| UPDATE 变慢 | 大范围更新、锁竞争 | 分批更新 |
| DELETE 变慢 | 大事务 | 分批删除、分区归档 |
| 分页变慢 | OFFSET 过大 | 游标分页 |
| 分区不提速 | 没有分区裁剪 | 优化分区条件 |
| 主从延迟 | 大事务、复制瓶颈 | 优化事务和复制 |
| 内存持续升高 | 连接内存、工作区问题 | 分析内存使用 |
| ORA-01555 | Undo 历史版本不足 | 检查长查询和 Undo |
| log file sync 高 | Commit、Redo 写入延迟 | 检查提交频率和存储 |
| MySQL Undo 堆积 | 长事务阻碍 Purge | 排查长事务 |
| 扩容后仍然慢 | SQL 或热点问题未解决 | 重新定位瓶颈 |
| 分库分表复杂 | 跨库查询和事务 | 优化数据边界 |
| 缓存与数据库不一致 | 更新顺序、复制延迟 | 一致性设计和补偿 |
ALTER TABLE orders
ADD INDEX idx_user_id(user_id);
风险:
建议:
不建议在业务高峰直接执行:
DELETE FROM game_log
WHERE create_time < '2025-01-01';
如果匹配几亿条数据,可能导致:
优化方案:
例如:
max_connections=10000
并不意味着数据库可以同时高效执行10000个复杂 SQL。
可能导致:
推荐:
统计信息更新后,执行计划可能变化。
风险:
原执行计划
↓
更新统计信息
↓
优化器重新选择计划
↓
关键SQL性能下降
建议:
优化不能只凭感觉。
必须对比优化前后的指标。
| 指标 | 优化前 | 优化后 |
|---|---|---|
| SQL P95 | 800ms | 80ms |
| SQL P99 | 3s | 300ms |
| QPS | 3000 | 6000 |
| CPU | 90% | 55% |
| 锁等待 | 频繁 | 明显减少 |
| 复制延迟 | 15s | 1s |
以上为示例数据,不代表保证收益。
验证流程:
记录性能基线
↓
准备测试数据
↓
复现业务负载
↓
实施单项优化
↓
执行压力测试
↓
对比性能指标
↓
验证数据一致性
↓
验证回滚方案
↓
灰度上线
↓
持续监控
重点关注:
以下数据规模仅用于规划参考,不是固定的架构切换阈值。
| 数据规模 | MySQL | Oracle |
|---|---|---|
| 100万 | SQL、索引 | SQL、索引、统计信息 |
| 1000万 | 慢查询治理、缓存 | 执行计划、统计信息 |
| 1亿 | 归档、分区、读写分离评估 | 分区、SGA/PGA、索引策略 |
| 10亿 | 冷热分离、分片评估 | 分区、并行与存储优化 |
| 100亿 | 分布式架构、数据分层 | 分布式扩展、分析系统分离 |
| 更大规模 | 多层存储与服务化数据架构 | 按负载选择分布式方案 |
注意:
不要因为一张表达到1亿条数据就直接分库分表。
是否需要分库分表,取决于:
数据库性能下降
│
▼
采集性能指标
│
▼
判断瓶颈类型
│
├── CPU高
│ ├── 高频SQL
│ ├── 复杂计算
│ └── 执行计划异常
│
├── I/O高
│ ├── 全表扫描
│ ├── 缓存不足
│ └── 大量写入
│
├── 锁等待高
│ ├── 长事务
│ ├── 热点行
│ └── 加锁顺序问题
│
├── 内存压力
│ ├── 缓存配置
│ ├── 连接内存
│ └── 排序工作区
│
└── 容量压力
├── 历史归档
├── 分区
├── 冷热分离
└── 分库分表
│
▼
制定优化方案
│
▼
测试环境验证
│
▼
灰度上线
│
▼
持续监控
需要重点掌握:
需要重点掌握:
重点解决:
核心目标:
提高订单处理能力,保证库存和支付正确性。
重点解决:
核心目标:
降低角色数据访问延迟,保证虚拟资产正确性,支撑大量玩家同时在线。
数据库性能调优可以概括为六个关键词:
SQL、索引、事务、内存、数据分层、架构。
主要优化方向:
SQL优化
↓
索引优化
↓
InnoDB参数优化
↓
事务与锁优化
↓
分区与归档
↓
缓存与读写分离
↓
分库分表
主要优化方向:
SQL优化
↓
执行计划分析
↓
统计信息优化
↓
索引与分区优化
↓
等待事件分析
↓
SGA/PGA优化
↓
Redo/Undo优化
↓
集群与架构优化
数据库性能优化的本质,不是简单增加服务器资源,而是让高频业务以更低的资源成本访问必要的数据,同时让历史数据不干扰核心交易。
当数据规模达到数十亿甚至百亿级时,需要从单纯的数据库参数优化,逐步转向数据生命周期管理、缓存、分布式架构以及在线交易与分析型负载分离。
最终目标:高性能、高并发、高可用、数据一致性、可扩展性。