各位,我是老张。今天从一次实战出发,往下拆一层。
先说一句结论。什么是关系型数据库?它是用关系模型组织数据、以二维表存储、用 SQL 操作、并靠事务保证一致性的数据库系统。 这句话你背下来了,面试也够用了。
但问题就在这。我见过太多人把这句话背得滚瓜烂熟,一上生产就懵。几年前我参与过一个秒杀系统,用的正是关系型数据库,标准配置。上线第一晚,库存超卖了三百多件。事后复盘,没人答得上来:数据库明明支持事务,为什么还是超了?
所以这篇不重复定义。我要回答的是那句定义背后的问题:这五个字,在你脚下到底是怎么跑起来的。
定义之外,你可能答不上来的三个问题
先把话说直白。搜"什么是关系型数据库",你大概率会得到一份标准答案:关系模型、二维表、主键外键、SQL、ACID,再加上 MySQL、Oracle 这些例子。这些答案挑不出错,也补不出新东西,几十个字就讲完了。定义本身已经没有信息增量的空间了。
真正拉开差距的,是另外几个问题。那张订单表里的"关系",到底约束的是什么?你敲下 COMMIT 之后,磁盘上到底发生了什么?事务说好的隔离性,又是拿什么东西换来的?这几个问题,随便一个答不上来,真出事故的时候就只能干瞪眼。下面逐层拆。
关系型数据库的"关系",到底约束了什么
很多人把"关系"理解成表跟表的外键。这个理解不算错,但太浅。关系模型里的"关系",本质是一张表,行是元组,列是属性。表与表之间的联系,靠主键唯一标识一行,靠外键指向另一张表的主键。很多人只看到"能连起来"这一面,其实真正值钱的是"能被约束"。
说白一点,约束才是关系型的命根。类型约束、非空约束、唯一约束、外键约束、检查约束,它们把"数据不能乱"这件事写进了数据库。为什么金融、电信这类系统死磕关系型数据库?因为业务逻辑的一致性,最终要落到数据约束上。
举个最朴素的例子。账户表里余额这一列,声明成数值类型,加一个非负检查。那么任何试图把余额写成负数的操作,会在数据库层直接被拦下来,而不是等到对账那天才发现。
sql
CREATE TABLE t_account (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
balance NUMERIC(18,2) NOT NULL CHECK (balance >= 0)
);
这段约束看着不起眼,但它是"关系"的第一层含义:数据库替你守住了数据的底线。
一次提交,在磁盘上到底发生了什么
回到那次超卖。要讲清它,得先知道一次事务提交背后发生了什么。事务的四个特性,缩写是 ACID,原子性、一致性、隔离性、持久性。这四个词人人会背,但它们不是免费的,每一个都要靠实打实的机制去换。
先说原子性和持久性。这两个靠的是先写日志,再写数据 。你敲下 COMMIT 的那一刻,数据库不会把数据页直接刷到磁盘,它先把这次改动的日志落盘。以 KES 为例,事务提交的流程是这样的:先把事务相关的 REDO 日志从缓冲区写回磁盘上的日志文件,然后释放这个事务持有的锁,最后才把事务标记为已完成。数据页的落盘是后面 checkpoint 阶段的事。
sql
-- 一个最普通的转账事务
BEGIN;
UPDATE t_account SET balance = balance - 100 WHERE id = 1;
UPDATE t_account SET balance = balance + 100 WHERE id = 2;
COMMIT;
这个顺序很关键。日志先落地,就算下一秒机器断电,重启后也能靠日志把没写完的数据重做出来。这就是持久性。反过来,没提交的事务要回滚,也是拿日志去逐步撤销。那超卖跟这个有什么关系?因为原子性管的是"要么全做要么全不做",管不了"两个事务同时读到同一个库存"。那是隔离性的活。
隔离性:怎么让并发的事务"看不见彼此"
超卖的根因,几乎都是隔离性没设对。事务之间相互干扰,会产生四类问题:脏读、不可重复读、幻读,还有更隐蔽的写偏差。为了压制它们,SQL 标准定义了四个隔离级别。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 实现代价 |
|---|---|---|---|---|
| 读未提交 | 可能 | 可能 | 可能 | 最低 |
| 读已提交 | 不会 | 可能 | 可能 | 较低 |
| 可重复读 | 不会 | 不会 | 理论上可能 | 较高 |
| 串行化 | 不会 | 不会 | 不会 | 最高 |
很多数据库默认给的是"读已提交"。这个级别下,同一事务里两次读同一行,结果可能不一样。秒杀那种"先查库存再扣减"的写法,恰好踩在这个缝上。
sql
-- 危险的写法:查和扣分两步,中间有窗口
SELECT stock FROM t_stock WHERE sku = 'A001';
-- ... 应用层判断 stock > 0 ...
UPDATE t_stock SET stock = stock - 1 WHERE sku = 'A001';
两个请求同时走到查询那一步,都读到库存为 1,都判断"够扣",于是都去扣。超卖就这么来的。正确的做法是把判断和扣减压进一条语句,或者显式加锁把窗口关掉。
底层靠什么实现隔离?两条路。一条是锁 ,通过两段锁协议保证并发调度的结果可以串行化。另一条是 MVCC,多版本并发控制。MVCC 给数据保留多个版本,读操作去读某个时间点的快照,不用去等写操作。这样读写不互相阻塞,并发能力就上来了。理解了这层,你就明白为什么"关系型数据库是不是过时了"这种问题没有意义。它处理并发一致性的这套机制,是几十年工程打磨出来的,换个存储模型绕不过去。
索引为什么快:B+Tree 和存储引擎
再往下挖一层,就碰到存储引擎了。你写的表和索引,物理上到底放在哪、怎么组织,这是存储引擎管的。拿 KES 来说,它的存储引擎是 heap 表引擎,负责存放用户表和系统表的实际数据以及索引数据。
这个引擎不是孤立的。它跟 WAL 日志、checkpoint、缓冲池、关系缓存这几块是强相关的。你查一条数据,背后是缓冲池在内存和磁盘之间搬运页面;你改一条数据,WAL 负责留痕;checkpoint 负责把脏页刷下去。这几块拼起来,才构成一次完整的读写和事务链路。
那索引为什么能把查询从全表扫描压成几毫秒?主流关系型数据库的索引结构是 B+Tree。它有两条特性最关键。
一是树矮 。B+Tree 一个节点能放下几百个键,三到四层就能撑起上亿行数据,查一次最多几次磁盘 IO。二是叶子节点连成有序链表。范围查询顺着链表扫就行,不用回到根节点重走。
这里有个实战经验。索引不是越多越好。每个索引都会拖慢写入,因为写数据时得同步维护所有索引。见过一张表挂了十几个索引,写入慢得像蜗牛,最后砍掉一半,写入直接翻倍。这个是踩过坑的,不用怀疑。
那到底怎么选:关系型 vs 非关系型
拆完内核,回到选型。这也是搜"什么是关系型数据库"的人真正关心的问题。
| 维度 | 关系型数据库 | 非关系型数据库 |
|---|---|---|
| 数据模型 | 二维表,强结构 | 键值、文档、列族、图 |
| 事务 | 完整 ACID | 多数只保证单行或最终一致 |
| 查询语言 | 标准 SQL | 各家自有 API |
| 扩展方式 | 垂直扩展为主,可集群 | 原生水平扩展 |
| 适合场景 | 交易、账务、强一致业务 | 日志、缓存、海量稀疏数据 |
| 国产化适配 | 有成熟的国产替代(如 KES) | 生态以开源社区版本为主 |
| 代表产品 | KES、Oracle、MySQL | Redis、MongoDB、HBase |
结论很清楚。要强一致、要复杂关联查询、要事务兜底,选关系型。要很高的写入吞吐、要灵活的 schema、能接受最终一致,选非关系型。
现实中大部分核心系统是关系型打底,非关系型做补充。缓存放 Redis,日志放 ES,账务和订单老老实实放关系型数据库。这不是保守,是权衡。
信创场景下,一个关系型数据库该怎么挑
把视角切到国产化替代。这几年很多单位在做信创改造,问法也从"什么是关系型数据库"变成了"用哪个关系型数据库"。挑国产关系型数据库,我一般看四个维度。
第一看迁移成本。老系统里沉淀了大量 Oracle 的存储过程、内置函数和系统视图,能不能少改甚至不改是关键。KES 支持内核级的兼容模式,数据库类型为 KINGBASE 时,可以选择 ORACLE、MYSQL、SQLSERVER 等不同兼容模式,不同模式下内核支持的能力不一样。说到底,这种内核级兼容图的就是一件事:Oracle 的存储过程少改甚至不改,这一点直接决定迁移工期。
第二看高并发承载。核心业务系统的 OLTP 压力是硬指标,账务、订单这类场景对事务吞吐和响应延迟都很敏感。选型时别只看参数,要看真实业务负载下跑出来的表现。
第三看高可用与数据同步。KES 的主从同步支持半同步或强同步,避免数据丢失。配合 Kingbase FlySync(金仓异构数据同步软件),可以先把源系统和新建系统的数据实时同步,选业务低峰期做割接,把停机时间压到最低。
第四看工程化落地能力。有没有成熟的迁移工具链,有没有可参考的同行业案例。
说到案例,金仓在金融和教育行业有不少落地。金融这边,它的新一代手机银行系统解决方案连续入选金融信创优秀解决方案,把用户中心、账户中心、限额中心等十二大业务做了整合迁移。教育这边,某高校的教务管理系统用这套方案承接了核心业务。这些案例的共性,是从"能用"往"好用"走,靠的是工程化,不是一句口号。
关系型数据库避坑清单
拆到最后,给你三份真金白银换来的教训。
- 先查库存再扣减,这种两步写法在秒杀和扣减场景就是事故源头。两个请求同时读到库存 1、都判断够扣,扣两次就超卖;能压成一条带条件的 UPDATE 就压,压不了就显式加锁,别指望默认隔离级别兜底。
- COMMIT 返回成功,不等于数据已经安全落盘。日志先于数据页落盘这个顺序,决定了哪些参数动了会丢数据;上线前把"是否等日志刷盘再返回"这类开关的语义摸清楚,别等断电重启才发现日志没落。
- 索引别贪多。每次写入都要同步维护所有索引,挂得越多写得越慢;见过一张表挂十几个索引、写入慢得像蜗牛,砍掉一半才恢复,建索引要跟着查询模式走,不能跟着直觉走。
小结
回到开头那个问题。什么是关系型数据库?它是关系模型、二维表、SQL 和 ACID 的组合,但更准确的说法是:**它是一整套用约束守住底线、用日志保证持久、用 MVCC 和锁撑起并发、用 B+Tree 加速查询的系统工程。**它的价值,藏在定义背后的这套机制里。这也是为什么信创选型时,光看"是不是关系型"远远不够,得看内核做得扎不扎实。
落到国产替代,金仓(KES)走的是自主研发这条路。它能帮上什么忙,说白了就三件事:迁移时让 Oracle 的存储过程少改甚至不改,割接时用 Kingbase FlySync 做实时同步、不用停机,运行时靠主从强同步兜底、不丢数据。从"能用"到"好用",靠的就是这些做得扎实,不是一句口号。对正在做信创改造的团队来说,值得认真评估。
你所在的核心系统,目前用的是哪种关系型数据库?迁移时最头疼的是存储过程改写,还是数据同步?欢迎在评论区聊聊,我挑典型的在下篇拆。
我是底层玩家老张。咱不聊概念,只聊源码和压测跑出来的东西。