Skip to content

数据库出现

写过小脚本的人都知道,数据量小的时候写个 JSON、CSV、纯文本就完事了——简单直接,不用装数据库。但只要场景升级到"多人同时访问的网站",文件方案很快就崩:两个人同时写文件会互相覆盖,按条件查数据要把整个文件扫一遍,误删之后恢复基本靠运气,字段格式也没法统一检查。数据库就是专门解决这一堆问题的系统。

一、文件方案的局限

把文件方案的问题列一下,基本就明白为什么要数据库了:

  • 查询慢:找一个用户要把整个文件从头扫到尾,百万行数据的时候基本没法用
  • 并发难:两个进程同时写文件,后写的覆盖先写的,完全没有并发控制
  • 格式脆弱:JSON 里少个逗号、CSV 里多一个换行,整份文件都解析不了
  • 约束少:邮箱格式对不对、金额是不是负数、状态值合不合法,全靠业务代码自己检查
  • 恢复麻烦:误删一行或者写坏了,只能翻备份,而且备份可能也是坏的

数据库里的"表"看起来跟结构化文件差不多——也是行和列——但多了字段类型、约束(NOT NULL、UNIQUE、外键)、索引、事务、查询语言这些能力。这些都是文件方案天生没有的。

二、表和 SQL

数据库里最基本的概念就是表。表里每一行是一条记录,每一列是一个字段。比如一个用户表:

idnameemailcreated_at
1alicealice@example.com2026-06-10
2bobbob@example.com2026-06-10

操作这些表用的是 SQL(结构化查询语言)——SELECT 查、INSERT 插、UPDATE 改、DELETE 删,加上 WHERE 过滤、JOIN 关联、GROUP BY 聚合。SQL 学起来不难,但写好并不容易,索引设计、查询优化、连接池管理这些是 DBA 和后端老手的核心技能。

数据库分好几类,根据数据结构选:

  • 关系型(MySQL、PostgreSQL):数据是结构化的,有明确表结构和关系,适合用户、订单、权限、审计这类核心业务数据
  • 文档型(MongoDB):数据是文档结构(JSON-like),字段灵活,适合内容管理、日志这种结构不固定的场景
  • 缓存型(Redis):内存数据库,主要做缓存和轻量状态存储,极快但不能存重要数据(丢了就丢了)
  • 检索型(Elasticsearch):专门做全文检索和日志查询,搜索引擎那种"输入关键词返回相关结果"的场景

业务里通常会同时用好几种——MySQL 存核心数据、Redis 做缓存、ES 做搜索、MongoDB 存日志,各管一摊。

三、事务

事务是数据库最核心的概念之一,处理的是"一组操作必须整体成功或者整体失败"这种需求。

最经典的例子就是转账:A 账户扣 100,B 账户加 100。如果扣完 A 的钱程序崩了,B 那边没加上,钱就凭空少了 100——这种事在金融系统里是绝对不能发生的。事务把这两步包在一起,要么两步都成功提交,要么任何一步失败就整体回滚,绝对不会出现"扣了钱但没加上"这种中间状态。

事务有四个特性,叫 ACID:

特性干什么
原子性(Atomicity)一组操作整体成功或整体失败,不会停在中间状态
一致性(Consistency)操作前后数据必须满足所有约束(外键、唯一、检查约束)
隔离性(Isolation)并发事务之间的影响受控制,看起来像串行执行
持久性(Durability)提交之后的数据不会因为进程崩溃或者断电而丢失

实际业务里"创建任务并写审计日志"、"扣库存并生成订单"、"更新配置并记录版本"这些动作都需要事务包着,不然任何一步失败都会留下脏数据。

四、索引

索引说白了就是数据库给某些字段额外建的查找目录,类似书的目录——没有索引找一个用户要翻整本书(全表扫描),有索引直接定位到第几页(索引查找)。

索引不是越多越好。索引会占磁盘空间,而且每次写入(INSERT/UPDATE/DELETE)都要同步维护索引,索引多了写入就会变慢。一般原则是:经常出现在 WHERE 条件、JOIN 关联、ORDER BY 排序里的字段适合建索引;很少查、频繁改的字段建索引得不偿失

举个具体例子:任务表 100 万行,接口要按 statuscreated_at 查最近失败的任务。没有合适索引时,数据库每次都要扫整张表(或者扫大量历史记录),用户看到接口 5 秒才返回。给 (status, created_at) 加个联合索引之后,同样的查询从全表扫描变成索引范围扫描,响应时间从 5 秒降到几十毫秒——这种提升在慢 SQL 优化里非常常见。

五、缓存

数据库再快也有上限,有些数据被反复查、又不怎么变,每次都打数据库太浪费。缓存就是把这些热数据放到更快的存储里(基本就是 Redis),应用先查缓存、查不到再查数据库然后回填缓存。打个比方,数据库像图书馆,缓存像你家书架——经常看的书放书架上随手拿,不常看的留图书馆。

适合缓存的数据有几个特征:读得多、变化不频繁、短时间内允许轻微延迟。用户权限、配置项、排行榜、热点字典、统计接口的结果,都是典型缓存对象。

但缓存引入自己的问题,经典的几个:

问题是什么怎么应对
缓存穿透大量查询根本不存在的数据,缓存里没有,全部打到数据库缓存空值、布隆过滤器
缓存击穿某个热点 key 突然过期,瞬间大量请求同时查数据库互斥锁、热点 key 永不过期
缓存雪崩大量 key 同时过期(或者缓存整体挂了),数据库压力瞬间暴涨过期时间加随机抖动、多级缓存
数据不一致数据库更新了但缓存还是旧值,用户看到老数据主动失效、延迟双删、订阅 binlog 同步

核心原则:缓存只是优化手段,不能作为数据来源。缓存里丢了数据,系统必须能从数据库恢复出来——不然 Redis 一重启业务就瘫了。

六、复制与备份

数据库的高可用和容灾,主要靠这两种机制:

机制解决什么问题
主从复制主库故障时从库可以接管;读请求可以分摊到从库
备份误删、表损坏、机房整体故障后能恢复数据
binlog/WAL记录所有数据变更过程,支持基于时间点的恢复(PITR)和复制

很多人会混淆这两个概念,但它们解决的是完全不同的问题:

复制不能替代备份。主从复制是异步的(或者半同步),主库上误删了一张表,这个 DROP 操作会被同步到所有从库——结果所有副本里这张表都没了。这时候只能靠备份恢复。

备份不能替代高可用。备份恢复需要时间——几十 GB 的数据库恢复可能要几十分钟到几小时,期间业务完全不可用。所以核心业务必须有主从切换或者集群方案,不能光靠备份兜底。

实际生产里两套都要做:复制保证主库挂了能切换、备份保证数据被误删能恢复、binlog 保证能恢复到精确的时间点。任何一个环节缺失,出事的时候就会非常被动。