开发转兼职DBA(六):换了个数据库,问题还是那些问题
前五篇讲的几乎都是Oracle。但如果你理解了底层原理,换到MySQL、PostgreSQL、SQL Server------问题变了外衣,骨架还是那个骨架。这篇做一个跨数据库的对照,看看哪些知识是"一次学会,到处使用"的。
文章目录
- 开发转兼职DBA(六):换了个数据库,问题还是那些问题
-
- 系列回顾
- 一、WAL------所有数据库都这么干
-
- Oracle
- MySQL (InnoDB)
- PostgreSQL
- [SQL Server](#SQL Server)
- 对照
- 二、MVCC------实现不同,目标一样
- 三、事务隔离级别
- 四、索引------B+树是标配
- 五、备份恢复
- 六、监控
- 七、一次学会,到处使用
- 总结:问题没变,只是解法换了名字
系列回顾
- 只会写SQL的那几年------DML/DDL,能跑就行
- 执行计划教我做事------全表扫描、索引、复合索引、统计信息
- 数据库起不来了------WAL、redo日志、事务持久性
- 又起不来了------MVCC、undo、事务隔离、并发控制
- 从救火到防火------参数、内存、监控、备份
- 换了个数据库,问题还是那些问题(本篇)
一、WAL------所有数据库都这么干
Oracle
用户修改数据
↓
生成redo记录 → 写入Log Buffer → 提交时刷到redo log文件
↓
后台进程把脏块写到数据文件
Oracle的redo log是循环使用的,默认三组。可以配置归档模式(ARCHIVELOG),日志切换时自动归档,支持时间点恢复。
MySQL (InnoDB)
用户修改数据
↓
生成redo记录 → 写入redo log buffer → 提交时刷到ib_logfile0/ib_logfile1
↓
后台线程把脏页写到.ibd数据文件
InnoDB的redo log也是循环使用,两个文件(ib_logfile0、ib_logfile1)。MySQL还有binlog------用于主从复制和时间点恢复,和redo log是两套独立的日志。
sql
-- MySQL查看redo log状态
SHOW ENGINE INNODB STATUS;
-- binlog相关
SHOW BINARY LOGS;
SHOW BINLOG EVENTS IN 'mysql-bin.000001';
PostgreSQL
PostgreSQL的WAL文件就叫WAL,不叫redo log。存放在pg_wal/目录下。
sql
-- 查看WAL配置
SHOW wal_level;
SHOW max_wal_size;
SHOW checkpoint_completion_target;
PostgreSQL的WAL还有一个独特用途:逻辑复制和CDC(Change Data Capture)。通过解析WAL,可以实时捕获数据变更。
SQL Server
SQL Server叫Transaction Log,每个数据库有自己的日志文件(.ldf)。
sql
-- 查看事务日志
DBCC SQLPERF(LOGSPACE);
-- 查看日志恢复模式
SELECT name, recovery_model_desc FROM sys.databases;
对照
| 数据库 | 叫什么 | 文件 | 循环使用 | 归档支持 |
|---|---|---|---|---|
| Oracle | Redo Log | redo01.log, redo02.log, redo03.log | 是 | ARCHIVELOG模式 |
| MySQL | Redo Log + Binlog | ib_logfile0, ib_logfile1 | 是 | binlog持久化 |
| PostgreSQL | WAL | pg_wal/下的分段文件 | 是 | WAL归档 |
| SQL Server | Transaction Log | database_name.ldf | 是(日志截断后) | 完整恢复模式 |
核心原理一样:先写日志,再写数据。日志落盘 = 事务持久化。崩溃后用日志重做。
二、MVCC------实现不同,目标一样
Oracle
MVCC通过undo段实现。数据块里只有最新版本,旧版本在undo段里。
数据块:id=1, xm='李四'(事务A修改后的值)
undo段:id=1, xm='张三'(事务A修改前的值)
事务B读取时,如果B的SCN早于A的修改,去undo段里找旧值。
MySQL (InnoDB)
MVCC通过undo log实现。和Oracle类似,旧版本存在undo log里。
InnoDB每行数据有两个隐藏列:
DB_TRX_ID:最后修改这行的事务IDDB_ROLL_PTR:指向undo log的回滚指针
读取时,根据当前事务的"读视图"(Read View)判断哪行对自己可见。如果最新版本不可见,沿着DB_ROLL_PTR找到旧版本。
PostgreSQL
MVCC的实现方式不一样------旧版本和新版本直接存在同一个表里。
每行数据有:
xmin:插入这行的事务IDxmax:删除这行的事务ID(0表示未被删除)- 更新 = 删除旧行 + 插入新行
读取时,判断xmin和xmax是否在当前事务的可见范围内。
代价:表容易膨胀。旧版本留在表里,需要VACUUM清理。
sql
-- PostgreSQL查看表的膨胀情况
SELECT schemaname, relname, n_dead_tup, n_live_tup
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC;
对照
| 数据库 | 旧版本存在哪 | 读取时怎么判断 | 表膨胀问题 |
|---|---|---|---|
| Oracle | undo段 | SCN比较 | 无 |
| MySQL | undo log | Read View + DB_TRX_ID | 无 |
| PostgreSQL | 数据表本身 | xmin/xmax比较 | 有,需要VACUUM |
| SQL Server | tempdb | 行版本存储 | tempdb空间增长 |
核心原理一样:写不阻塞读,读不阻塞写。每个事务看到一致性快照。
三、事务隔离级别
SQL标准定义了四个隔离级别,各数据库的实现有差异:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 |
| READ COMMITTED | 不会 | 可能 | 可能 |
| REPEATABLE READ | 不会 | 不会 | 可能 |
| SERIALIZABLE | 不会 | 不会 | 不会 |
各数据库的默认隔离级别
| 数据库 | 默认隔离级别 | 实现方式 |
|---|---|---|
| Oracle | READ COMMITTED | MVCC + undo |
| MySQL (InnoDB) | REPEATABLE READ | MVCC + Next-Key Lock(间隙锁,防止幻读) |
| PostgreSQL | READ COMMITTED | MVCC + xmin/xmax |
| SQL Server | READ COMMITTED | 默认用锁,可开启MVCC(RCSI) |
MySQL的特殊之处
MySQL默认REPEATABLE READ,而且在InnoDB的实现中,通过**间隙锁(Gap Lock)**防止幻读------比SQL标准的REPEATABLE READ更强。
sql
-- MySQL查看隔离级别
SELECT @@transaction_isolation;
-- 设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
Oracle的特殊之处
Oracle不支持READ UNCOMMITTED和REPEATABLE READ。只有READ COMMITTED和SERIALIZABLE两种。READ COMMITTED是默认的------每条SQL执行时获取新的一致性快照。
sql
-- Oracle设置隔离级别
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
Oracle的SERIALIZABLE不是真的串行执行------而是事务开始时获取一个SCN,后续所有读操作都看到这个SCN时刻的数据快照。如果事务尝试修改已经被其他事务修改的数据,报ORA-08177: can't serialize access for this transaction。
四、索引------B+树是标配
各数据库的索引类型
| 数据库 | 主索引 | B+树 | Hash | 全文 | 其他 |
|---|---|---|---|---|---|
| Oracle | B+树 | 有 | 有(内存列) | 有(CTXSYS) | 位图索引、函数索引 |
| MySQL (InnoDB) | 聚簇索引(B+树) | 有 | 有(MEMORY引擎) | 有(FULLTEXT) | 空间索引(R-Tree) |
| PostgreSQL | 堆表 | 有 | 有 | 有(GIN/GIST) | BRIN、SP-GiST |
| SQL Server | 聚簇索引(B+树) | 有 | 无内置 | 有(全文搜索) | 列存储索引、XML索引 |
MySQL的聚簇索引
MySQL InnoDB的主键索引是聚簇索引 ------数据文件本身就是按主键组织的B+树。二级索引的叶子节点存的是主键值,不是行号。
聚簇索引(主键):
叶子节点存完整行数据
二级索引(xm):
叶子节点存 主键值 → 回聚簇索引查完整数据
这意味着:用二级索引查数据,要查两次索引(一次二级索引,一次聚簇索引)。如果查询只需要索引列和主键列,就不需要回表------索引覆盖。
Oracle的堆表
Oracle默认是堆表------数据没有特定顺序,存在哪由空间管理决定。索引的叶子节点存的是ROWID(物理行地址),可以直接定位到数据块。
索引(xm):
叶子节点存 ROWID → 直接到数据块取数据
Oracle的索引回表通常比MySQL快------ROWID直接指向物理位置,不需要再查一次索引。
查看执行计划
sql
-- Oracle
EXPLAIN PLAN FOR SELECT * FROM t WHERE xm = '张三';
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
-- MySQL
EXPLAIN SELECT * FROM t WHERE xm = '张三';
-- PostgreSQL
EXPLAIN ANALYZE SELECT * FROM t WHERE xm = '张三';
-- SQL Server
SET SHOWPLAN_TEXT ON;
GO
SELECT * FROM t WHERE xm = '张三';
GO
五、备份恢复
各数据库的备份方式
| 数据库 | 物理备份工具 | 逻辑备份工具 | 在线备份 |
|---|---|---|---|
| Oracle | RMAN | expdp/impdp | ARCHIVELOG模式 |
| MySQL | mysqlbackup/xtrabackup | mysqldump | binlog + LVM快照 |
| PostgreSQL | pg_basebackup | pg_dump/pg_restore | WAL归档 + 复制槽 |
| SQL Server | SQL Server备份 | BCP/SSIS | 完整恢复模式 |
恢复到指定时间点
Oracle:
sql
RMAN> RUN {
SET UNTIL TIME "TO_DATE('2024-01-15 14:00:00', 'YYYY-MM-DD HH24:MI:SS')";
RESTORE DATABASE;
RECOVER DATABASE;
ALTER DATABASE OPEN RESETLOGS;
}
MySQL:
bash
mysqlbinlog --stop-datetime="2024-01-15 14:00:00" mysql-bin.000001 | mysql -u root -p
PostgreSQL:
restore_command = 'cp /archive/%f %p'
recovery_target_time = '2024-01-15 14:00:00'
SQL Server:
sql
RESTORE DATABASE mydb FROM DISK = 'backup.bak'
WITH STOPAT = '2024-01-15 14:00:00', RECOVERY;
做的事都一样:恢复全量备份 + 重放日志到指定时间点。
六、监控
各数据库的"慢查询"配置
Oracle: 自动采集(AWR),也可以手动追踪:
sql
ALTER SYSTEM SET sql_trace=TRUE;
MySQL:
sql
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 2; -- 超过2秒记录
PostgreSQL:
sql
ALTER SYSTEM SET log_min_duration_statement = 2000; -- 超过2秒记录
SQL Server:
sql
-- 扩展事件
CREATE EVENT SESSION [SlowQueries] ON SERVER
ADD EVENT sqlserver.rpc_completed(
WHERE duration > 2000000); -- 微秒
各数据库的"等待事件"
| 数据库 | 查等待事件 | 查慢SQL |
|---|---|---|
| Oracle | v$system_event |
v$sql |
| MySQL | performance_schema.events_waits_summary |
sys.statements_with_runtimes_in_95th_percentile |
| PostgreSQL | pg_stat_activity |
pg_stat_statements |
| SQL Server | sys.dm_os_wait_stats |
sys.dm_exec_query_stats |
做的事都一样:查数据库时间花在哪了,查哪条SQL最慢,查谁在等谁。
七、一次学会,到处使用
把前五篇学的东西做一个跨数据库映射:
| 概念 | Oracle | MySQL | PostgreSQL | SQL Server |
|---|---|---|---|---|
| WAL | Redo Log | Redo Log + Binlog | WAL | Transaction Log |
| MVCC旧版本 | undo段 | undo log | 数据表内 | tempdb |
| 默认隔离级别 | READ COMMITTED | REPEATABLE READ | READ COMMITTED | READ COMMITTED |
| 执行计划 | DBMS_XPLAN | EXPLAIN | EXPLAIN ANALYZE | SHOWPLAN |
| 索引结构 | B+树 + ROWID | B+树 + 聚簇索引 | B+树 + CTID | B+树 + 聚簇索引 |
| 物理备份 | RMAN | xtrabackup | pg_basebackup | BACKUP DATABASE |
| 逻辑备份 | expdp | mysqldump | pg_dump | BCP |
| 慢查询 | AWR | slow_query_log | log_min_duration | Extended Events |
| 等待事件 | v$system_event | performance_schema | pg_stat_activity | dm_os_wait_stats |
名字不同,做的事情一样。
总结:问题没变,只是解法换了名字
这个系列从DML/DDL讲到WAL、MVCC、事务隔离、索引、备份恢复、监控。所有内容以Oracle为主线,但每个概念在其他数据库里都有对应。
数据库换了,语言换了,架构换了。但核心问题------数据怎么不丢、并发怎么控制、查询怎么优化、出事怎么恢复------从Oracle到MySQL到PostgreSQL,一个都没变。
如果你在Oracle上理解了WAL、理解了MVCC、理解了执行计划、经历过崩溃恢复------换到MySQL,你的学习曲线不是从零开始,而是从"名字不一样"开始。原理你已经懂了,剩下的只是查手册。
这就是底层原理的价值------它是跨数据库的通用货币。
标签:#DBA #Oracle #MySQL #PostgreSQL #SQL Server #WAL #MVCC #跨数据库 #底层原理