数据库管理系统已经是当今软件的重要组成部分。开源的MySQL、PostgreSQL、商用的Oracle数据库随处可见。几乎所有的服务都需要依赖数据库管理系统来存储数据。database-banner图1-数据库数据库不会丢失数据。这听起来像是理所当然的事情。持久化也应该是数据库最基本的保障,但是在这个复杂的世界里,保证数据不丢失是非常困难的。今天,我们可以找到很多导致数据丢失的数据库问题的例子:MongoDB在过去很长一段时间内都无法保证持久化,很容易丢失数据[^1];RocksDBDeleteRange函数导致的数据丢失问题[^2];腾讯云硬盘故障,导致初创公司线上生产数据完全丢失[^3];无论是开源数据库还是云服务提供商提供的服务,都可能发生数据丢失。本文将数据库数据丢失的原因归纳为以下几个方面,我们将对这些原因进行详细介绍:人为因素导致的运维和配置错误是造成数据库数据丢失的首要原因;数据丢失是由于数据库用来存储数据的磁盘损坏造成的;数据库的功能和实现复杂,数据不及时刷盘可能会丢失;人为错误人为错误是数据丢失的主要原因。在腾讯云数据丢失事故中,我们会发现虽然事故原因是硬件故障,但最终导致数据完整性丢失的是运维人员的操作不当:首先,启用数据校验默认在正常的数据迁移过程中,启用后可以有效检测和避免源端数据异常,保证迁移数据的正确性。但运维人员为加快完成搬迁任务,违规核实数据。第二,正常数据搬迁完成后,源仓库数据要保留24小时。小时用于异常搬迁情况下的数据恢复。但运维人员为了尽快降低仓库利用率,非法从源仓库恢复数据。减少人为失误的最佳方式是将数据备份、运维等操作标准化,使用自动化流程处理涉及数据安全的操作,从而降低人为干预的风险。对于软件工程师来说,我们应该对生产环境保持敬畏之心,认真执行生产环境中的所有操作,意识到所有操作都可能对线上运行的服务产生影响,从而降低类似问题发生的概率。硬件错误我们在为什么基础服务不应该高可用一文中介绍过,任何在线服务都能正常运行是极其偶然的,只要时间足够长,我们不能保证服务100%可用[^4]。如果磁盘等硬件使用时间足够长,则极有可能损坏。根据Google论文中的数据,硬盘5年内的年平均故障率(AnnualizedFailureRates,AFR)为8.6%[^5]。2018年腾讯云数据损坏事故是由于磁盘静默错误(Silentdatacorruption)导致的单副本数据错误[^6]。静态磁盘错误是磁盘固件或主机操作系统未检测到的错误,包括以下几种:电缆松动、电源不可靠、外部冲击、网络导致的数据丢失等。正是因为磁盘数据损坏是很常见,所以我们需要一种数据冗余的方法来保证在发生不可恢复的读取错误(UnrecoverableReadError)时能够恢复磁盘数据。独立磁盘冗余阵列(RAID)是一种数据存储虚拟化技术,可以将多个物理磁盘组合成一个逻辑磁盘,可以增加数据冗余,提高性能[^7]。raid-strategy图2-三种RAID策略RAID主要使用三种策略:Striping、Mirroring和Parity来管理磁盘中的数据。下面举几个简单的例子:RAID0使用了数据分段技术,但是没有镜像和奇偶校验。它几乎不保护磁盘上的数据,任何磁盘损坏都意味着其中的数据无法恢复,但因为没有冗余,它也会提供更好的性能;RAID1使用数据镜像功能,但没有奇偶校验和数据分段。所有数据将写入两个相同的磁盘,两个磁盘都可以提供外部数据读取服务。这种方法减少了磁盘使用,但可以提高读取性能并提供备份;...RAID使用的分段和镜像策略类似于分布式数据库中的分区和复制(Replication)。SegmentationSharding和sharding将数据切分并分布到不同的磁盘或机器上,而mirroring和replicas则用于复制数据。许多现代操作系统都提供基于软件的RAID实现,一些云服务厂商也采用自研文件系统或冗余备份机制:谷歌使用谷歌文件系统来管理文件,它将文件分块存储,并通过主服务器管理所有文件块[^8];微软在Azure中使用纠删码计算冗余数据[^9];硬件错误在生产环境中很常见,我们只能通过数据冗余和校验来降低数据丢失的可能性,但是增加冗余的方式只能不断降低数据丢失的概率,并不能100%避免。实施复杂的数据库管理系统最终会将数据存储在磁盘上。对于很多数据库来说,数据落到磁盘就意味着持久化完成了。磁盘作为数据库系统的下层,可以稳定地存储数据,是数据库持久化数据的基础。database-and-disk图3-数据库依赖于磁盘很多人误以为使用write就可以将数据写入磁盘,其实这是错误的。不仅write函数不保证数据写入磁盘,有些实现甚至不保证目标空间是为写入数据预留的[^10]。一般情况下,对文件的写入只会更新内存中的pagecache,而这些pagecache不会马上刷到磁盘,操作系统的flusher内核线程会在执行时将数据刷到磁盘满足以下条件[^11]:freememory脏页占用的内存空间下降到一定阈值时需要释放;如果脏数据持续一定时间,则将最早的数据写入磁盘;用户进程执行sync或fsync系统调用;如果我们想立即flush数据如果想写磁盘,需要在执行write后立即调用fsync[^12]等函数。当fsync等函数返回时,数据库会通知调用者数据写入成功。write-and-fsyn图4-Write和Fsyncwrite和fsync在数据库管理系统中非常重要,它们是提供持久性保证的核心方法,一些开发人员编写错误的代码会导致写数据丢失的错误理解。数据库除了持久化的特性外,可能还需要提供ACID(原子性、一致性、隔离性、持久性)或BASE(基本可用、软状态、最终一致性)保证,有的数据库还提供分片、副本、分布式事务等复杂功能的引入也增加了数据库系统的复杂度,随着程序复杂度的增加,出现问题的可能性也随之增加。小结数据库管理系统是软件工程中最复杂、最重要的系统之一。几乎所有服务的正常运行都是基于数据库不会丢失数据的假设。但是,由于以下原因,数据库并不能完全保证数据的安全:运维人员在配置和运维过程中,极有可能因为操作失误而丢失数据;数据库所依赖的底层磁盘出现硬件错误,导致数据无法恢复;数据库系统支持的功能多且复杂,数据放置不及时可能会丢失;一旦发生数据丢失事故,影响会非常大,我们在使用数据库存储核心业务数据时不能完全信任数据库,为了稳定性,可以考虑使用热备份和快照进行容灾。最后,让我们看看一些未解决的相关问题。有兴趣的读者可以仔细思考以下问题:除了文中列举的数据丢失事故,还有哪些数据库或云服务商发生过数据丢失?Redis的RDB和AOF机制什么时候把数据放到磁盘上?应该如何定义数据成功写入数据库?
