mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
597 字
2 分钟
为什么数据库会丢失数据
2023-05-09

数据库丢失数据听起来不可思议,但在某些场景下,数据库确实会丢失数据。本文深入解析数据库丢失数据的原因和防御机制。

一、数据库的存储层次#

1.1 存储金字塔#

flowchart TB subgraph 速度最快的存储 CPU[CPU Cache] end subgraph 速度快,容量小 RAM[内存] end subgraph 速度慢,容量大 SSD[SSD] end subgraph 速度最慢,容量最大 HDD[机械硬盘] end CPU --> RAM RAM --> SSD SSD --> HDD style CPU fill:#f96 style RAM fill:#ff9 style SSD fill:#9f9 style HDD fill:#9f9

1.2 数据库写入流程#

flowchart LR A[写请求] --> B[(1) WAL] B --> C[(2) 内存缓冲池] C --> D[(3) 刷新到磁盘] D --> E[(4) 存储] style B fill:#ff9 style C fill:#ff9 style D fill:#9f9 style E fill:#9f9

二、丢失数据的场景#

2.1 内存 vs 持久存储#

数据库使用内存缓冲来提高性能

# 写入流程
def write_to_db(data):
# 1. 写入 WAL(预写日志)
wal.append(data)
# 2. 更新内存缓冲
buffer.update(data)
# 3. 异步刷新到磁盘
# 如果在这之前断电...
return "success" # 返回成功,但数据还在内存!

问题:数据库返回”写入成功”时,数据可能还在内存中。

2.2 Write-Back 策略#

操作系统和数据库都使用 Write-Back 策略来提高性能:

flowchart LR A[写操作] --> B{策略选择} B -->|Write-Through| D[同步写入磁盘] B -->|Write-Back| C[写入缓存] C -->|异步| D Note over C: 性能高,但可能丢失数据 Note over D: 性能低,但数据安全

三、数据库的持久性机制#

3.1 预写日志(WAL)#

WAL(Write-Ahead Logging) 是数据库保证持久性的核心机制:

# WAL 的工作原理
def write_with_wal(data):
# 1. 先写入日志(必须先落盘)
wal.append(log_record)
fsync(wal) # 强制刷盘
# 2. 然后更新内存
buffer.update(data)
# 3. 定期将内存刷新到磁盘
# 如果 crash 发生,可以从 WAL 恢复

3.2 fsync 的作用#

# fsync 确保数据写入磁盘
import os
# 确保文件写入磁盘
os.fsync(file_handle)
# 不调用 fsync,数据可能在缓存中

fsync 的问题:太慢!每次写入都 fsync 会严重影响性能。

3.3 组提交(Group Commit)#

# 组提交:批量 fsync
def group_commit():
while True:
# 收集一批日志
logs = batch_collect(wal, timeout=5ms)
if logs:
# 一次性刷盘
for log in logs:
write(log)
fsync() # 一次 fsync
# 通知所有请求完成
for log in logs:
log.done()

四、为什么会丢失数据?#

4.1 配置问题#

# PostgreSQL 配置
fsync = on # 推荐开启
synchronous_commit = on # 推荐开启
full_page_writes = on # 推荐开启
# MySQL (InnoDB)
innodb_flush_log_at_trx_commit = 1 # 每次提交都刷盘
innodb_flush_log_at_trx_commit = 2 # 提交时写入文件缓存
innodb_flush_log_at_trx_commit = 0 # 最快,但最不安全
设置性能安全性
1最高
2中(断电可能丢事务)
0低(断电丢 1 秒数据)

4.2 电源故障#

sequenceDiagram participant App as 应用 participant DB as 数据库 participant OS as 操作系统 participant Disk as 磁盘 App->>DB: COMMIT DB->>OS: 写入数据 OS->>OS: 写入缓存 DB->>App: 提交成功 Note over Disk: 断电!缓存中的数据丢失 App-->>DB: 查询数据 DB-->>App: ???(数据丢失)

4.3 SSD 的 Write Hole 问题#

flowchart LR subgraph Write Hole 问题 W[WAL] --> F[fsync] F --> D[数据页] Note: 如果 fsync 成功但数据页未写入时断电 Note: 数据页恢复时是旧数据 end

五、如何防止数据丢失#

5.1 正确配置#

# PostgreSQL 最安全配置
fsync = on
synchronous_commit = on
full_page_writes = on
wal_level = replica
max_wal_senders = 3
# MySQL 最安全配置
innodb_flush_log_at_trx_commit = 1
innodb_support_xa = on
sync_binlog = 1

5.2 主从复制#

flowchart TB subgraph 同步复制 W[主库] --> S1[从库1] W --> S2[从库2] Note: 写入成功 = 主库 + 从库都确认 end subgraph 半同步复制 W2[主库] --> S3[从库] Note: 写入成功 = 主库写入 + 部分从库确认 end

5.3 定期备份#

# PostgreSQL 备份
pg_dump -Fc mydb > backup.dump
# MySQL 备份
mysqldump --single-transaction mydb > backup.sql

六、各数据库的持久性保证#

6.1 PostgreSQL#

设置崩溃恢复电源故障
fsync=on保证保证(如果磁盘有电池)
fsync=off不保证不保证
同步复制保证保证

6.2 MySQL (InnoDB)#

设置崩溃恢复电源故障
双 1 配置保证保证(如果 UPS)
延迟双一保证可能丢 1 秒

6.3 NoSQL 的权衡#

# Cassandra 的最终一致性
# 写入时:写入任意节点即可
# 配置:W + R > N 才能保证强一致性
# DynamoDB 的配置
Table:
WriteCapacityUnits: 100
BillingMode: PROVISIONED

七、总结#

数据库丢失数据的原因:

原因说明防御
内存未刷盘Write-Back 策略配置正确的刷盘策略
fsync 未调用性能优化跳过开启 fsync
SSD 缓存控制器缓存启用掉电保护
配置不当追求性能牺牲安全按需配置

核心原则

  1. 理解数据库的持久性保证
  2. 根据业务需求配置
  3. 不要相信”写入成功”就等于”数据安全”
  4. 备份是最后一道防线

参考引用#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

为什么数据库会丢失数据
https://blog.souloss.com/posts/why-the-design/why-databases-lose-data/
作者
Souloss
发布于
2023-05-09
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时