数据库学习笔记这名字听起来像学生时代的小组作业,但这几年我越来越觉得,它才是后端、数据、运维甚至做产品的人成长过程里最容易被低估的主线。你要是去看现在数据库相关的高频搜索,不是在问SQL怎么写、数据库怎么安装,就是在处理数据库死锁、同步工具、迁移失败、连接池配置、国产数据库适配这些真问题。这篇文章更像是我把几年积累的数据库学习笔记重新整理了一遍,不按教科书的目录走,而是按我踩坑的顺序走,内容覆盖事务与锁的完整认知、从建库到生产环境的实操细节、向量数据库和时序数据库选型,以及达梦、人大金仓这类国产库的适配思路。无论你是刚准备数据库课程设计、正在备考计算机三级,还是已经被生产环境死锁折磨到半夜的开发者,都能找到可以直接上手的部分。
1. 为什么数据库学习总在“增删改查”之后断层
1.1 多数入门资料只教你操作,没教你建模
从很长一段时间的搜索来看,数据库课程设计、数据库增删改查是很多人学习的起点,这两个词本身没什么问题,问题在于大多数人到这个地方就停了。SQL的增删改查本质上是“操作”,不是“设计”,你照着教程写select、insert、update很容易,真正难的是表结构怎么设计。我见过太多课程设计,订单表里直接存商品名称和用户姓名,用户表没有独立主键,价格字段用float,最后写复杂查询时各种join地狱,数据量稍微大一点就慢得没法看。
数据库学习真正的分水岭,是从“能执行SQL”变成“能设计出十年后不用推倒重来的表结构”。建模这部分,建议先理解实体关系,再把三范式吃透,最后学会有意识地反范式。以订单系统为例,用户、商品、订单、订单项一定要拆成独立表,理由很简单:避免数据冗余,避免“一个用户改了名字,所有历史订单里的用户名都跟着错”的更新异常。但完全遵守三范式也会出问题,比如报表场景每次都要关联四五张表,性能扛不住,所以实际项目里又会出现宽表、冗余字段、预聚合表。这个平衡点,才是数据库设计里最核心的内容。
1.2 真正该优先建立的三个认知模型
在我自己的数据库学习笔记里,排在最前面的不是某个命令,而是三个认知模型:存储、并发、故障。
第一个是存储模型。数据行存在数据页里,索引用B+树。为什么几乎所有关系型数据库都选B+树?因为这个结构天然适合磁盘IO。三层B+树就能存下几百万行数据,查询只需要几次磁盘IO,而且B+树把所有数据都放在叶子节点,范围查询非常顺滑。理解了这一点,你就能明白为什么“给where条件里的列建索引”这么重要,也就能明白为什么某些操作会导致索引失效。
第二个是并发模型。多个会话同时读写同一份数据,怎么保证不出错,靠的是事务、锁、MVCC。很多人觉得隔离级别就是个概念,其实它决定了你的系统是“数据正确但慢”还是“数据错误但快”。
第三个是故障模型。redo log负责崩溃恢复,undo log负责回滚,binlog负责主从复制和时间点恢复。三个日志各管一段,数据库出事之后怎么恢复、主从怎么同步,全落在这些日志上。
这三个模型不仅能拿来应付面试,实际排查死锁、慢查询、主从延迟的时候,靠的全是这些底层的理解。没有模型,你看到报错就是一堆乱码;有了模型,报错是在告诉你系统的哪个环节出了问题。
1.3 从“会写SQL”到“能解释SQL为什么慢”的跨越
很多人在数据库学习笔记里会记一堆SQL写法,但极少有人认真看执行计划。我建议你尽早养成一个习惯:任何一条慢SQL,都跑一遍EXPLAIN,看看它走没走索引、扫描了多少行、有没有临时表、有没有文件排序。
我见过的最典型问题有三个。第一个,where条件里的列没建索引,导致全表扫描;第二个,在索引列上用了函数包裹,比如where DATE(create_time) = '2025-01-01',索引直接失效,正确写法是范围查询;第三个,深分页,limit 1000000, 20,每次都要先扫一百万行再丢掉,这种场景改成基于上次查询结果的游标分页,性能差距是数量级的。
慢SQL不是玄学。所有性能问题最后都能落到存储结构和执行计划上。你每跑一次EXPLAIN,就等于给数据库做了一次体检,看得多了,慢慢就能猜到一个查询大概会怎么执行。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 事务与锁:学习曲线最陡的一段
2.1 事务隔离级别不是背概念,而是观察现象
数据库课程设计里很多人直接给方法加上@Transactional就完事,但为什么要分四个隔离级别,基本没人说得清。我的建议是不要死背概念,自己去开两个客户端实测一遍。
READ UNCOMMITTED,一个事务能读到另一个事务还没提交的修改。如果那个事务最后回滚了,你就读到了一份根本不存在的脏数据。READ COMMITTED解决了脏读,但是同一个事务里,两次相同的查询可能得到不同的结果,这就是不可重复读,适合并发要求高、对一致性和实时性不那么敏感的场景。REPEATABLE READ是MySQL的默认级别,同一事务里多次读取结果一致,但它仍然允许幻读——范围查询时,另一个事务往里面插了行,再次查询会多出新数据。SERIALIZABLE强制事务串行执行,正确性最高,但并发能力直线下降,生产环境很少直接用。
你动手做几个小实验,把脏读、不可重复读、幻读都亲眼看到一遍,再回头看书上那些定义,会顺很多。面试的时候能用自己的话讲出这个观察过程,比背概念可靠得多。
2.2 行锁、间隙锁、next-key lock 到底锁住了什么
InnoDB在可重复读隔离级别下,默认用next-key lock来解决幻读问题。它锁的不是一条记录,而是一段范围。举个例子,表t有id为1、3、5、7四条记录,一个事务执行select * from t where id between 3 and 5 for update,它不只是锁住id为3和5这两条记录,还可能会锁住(1,3)到(5,7)之间的间隙,导致另一个事务想在id=4的位置插入数据时,直接被阻塞。
为什么数据库要把锁范围搞得这么大?因为InnoDB要保证在可重复读级别下,一个事务执行范围查询时看到的行集合是固定的,不能前后不一致。间隙锁就是为了防止其他事务在这个范围里插入新行。
有个细节要格外注意:如果条件走的是唯一索引等值查询,而且记录已经存在,next-key lock会退化成行锁;如果是普通索引或者范围查询,锁间隙的可能性就很大。这个差别直接关系到并发写入性能,尤其是订单、库存这类高频写场景。业务里经常出现“明明只更新一条数据,为什么其他会话也被卡住”,大部分原因就是间隙锁在起作用。
2.3 死锁排查完整链路
死锁几乎是每个上生产环境的人都会遇到的事。我先说一个最典型的复现过程,你照着操作一遍,感受会非常直接。
两个会话,会话A先执行update account set balance = balance - 100 where id = 1,然后停住;会话B执行update account set balance = balance - 100 where id = 2,也停住。接着A执行update account set balance = balance - 100 where id = 2,B执行update account set balance = balance - 100 where id = 1。此时A持有id=1的行锁等待id=2,B持有id=2的行锁等待id=1,两边互不相让,死锁形成。InnoDB会自动检测到死锁,并回滚其中一个事务。
关键是怎么排查。第一板斧是SHOW ENGINE INNODB STATUS\G,输出结果里找到LATEST DETECTED DEADLOCK段落,它会明确告诉你两个事务各持有什么锁、正在等待什么锁、最近执行的SQL是什么。第二板斧是查information_schema.INNODB_TRX,看当前有哪些事务在跑,再结合INNODB_LOCK_WAITS看锁等待关系。第三板斧是把死锁发生的SQL拿回代码里看,找出加锁顺序不一致的地方。
解决的思路一般有四条:第一,规范加锁顺序,多个资源要更新时,业务代码里统一先按id排序,再依次更新;第二,缩小事务范围,把不相关的查询挪到事务外面,持有锁的时间越短,碰撞概率越低;第三,优化索引,避免更新时因为索引失效导致锁范围扩大到全表;第四,热点行拆分,比如秒杀场景的库存字段,不要把所有人的更新都打在一行上,可以预先拆成多个库存槽位。
2.4 审计日志为什么可能引发索引争用
数据库相关热词里有一条“数据库开启审计引起索引争用”,这个现象确实存在。我实际遇到过一次,一个业务库开启审计之后,业务高峰期经常出现大量的latch争用,应用侧表现为偶发超时。一开始以为是慢SQL,后来查出来是审计模块在抢资源。
原因是审计功能会为每条操作记录审计日志,写审计表或审计文件。如果审计表本身放在默认表空间和业务数据混在一起,高频业务写入和审计插入就会在同一个buffer pool里争抢数据页,尤其当审计表索引设计不合理时,所有插入都往索引的同一个叶子节点附近挤,产生严重的页锁竞争,也就是“索引争用”。
遇到这种情况,我的建议是:审计表要单独放到独立表空间甚至独立实例;开启审计前先做压测,观察TPS是否明显下跌;生产环境如果能用异步审计或者旁路采集,就不要把审计和核心交易写在同一个数据库实例里;另外审计数据一定要有保留策略,定期按月份归档清理,否则表无限膨胀,后续查询和清理都很难受。
3. 从建库到生产环境:那些必须亲手踩过的坑
3.1 唯一约束和已经有重复数据的列
搜索词里那句“mysql设置唯一已经有重复数据库”,我想了半天,其实就是新手常遇到的一个问题:想给一张表加上唯一约束,但这一列里已经存在重复数据,ALTER TABLE ADD UNIQUE KEY执行后直接报错。
正确顺序不是硬加索引,而是先把数据收拾干净。第一步,用select col, count() from t group by col having count() > 1找出所有重复值;第二步,按业务规则确定保留哪一条,通常保留id最小那条,其余做合并或删除;第三步,确认没有重复数据之后,再执行ALTER TABLE ADD UNIQUE KEY。
这里有个很容易被忽略的点:如果表数据量已经很大,几百万行甚至上千万行,直接加唯一索引会长时间锁表,业务会收到大量等待。生产环境最好借助在线DDL工具,比如pt-online-schema-change或gh-ost,在低峰期执行。多花点时间做准备,比事后回滚省心得多。
3.2 连接池参数不是越大越好
快速开发一套带数据库的软件,很多人只关心数据库本身,忽略了连接池这个隐蔽的地方。连接池参数如果随意设置,系统一上线就会出问题。
我常用的HikariCP参数大致是这样:connectionTimeout设30000毫秒,idleTimeout设600000毫秒,maxLifetime设1800000毫秒,maximumPoolSize根据机器核数设,minimumIdle设5左右。很多人会把maximumPoolSize直接改成100甚至200,觉得连接多一点肯定好,但事实不是这样。数据库服务端每个连接都要分配内存和线程资源,连接数过多时,CPU的时间全部耗在上下文切换上,实际TPS不升反降。
连接池大小可以参考一个经验公式:maximumPoolSize = (核心线程数 * 2) + 有效磁盘数。举个实际例子,4核、单块SSD的机器,配10左右就够用了,盲目调到50以上往往会起反作用。连接池的另一个坑是连接失效,如果数据库或网络做了空闲回收,连接池里的连接可能已经被服务端断了,业务拿到手才发现连不上。解决方式是配置连接池层面的检测机制,同时让maxLifetime小于数据库的wait_timeout。
3.3 数据同步、CDC 与复制
数据库相关搜索里,同步工具和同步软件一直很热,尤其是“达梦数据库如何开启CDC”这类问题。先说原理,主从复制的本质是主库把变更写进binlog,从库拉取binlog并在本地重放。CDC,也就是变更数据捕获,本质上也是解析日志拿到增量变更,再投递到消息队列或者下游目标库。
我做过最简单的实验:给MySQL开binlog,而且要row格式,创建一个同步账号,然后用Canal订阅binlog,把变更实时解析出来投递到另一个库。这套链路跑通之后,你对主从复制和数据同步的理解会一下子落地。国产数据库要开启CDC,思路差不多,一般需要先开启归档日志、配置日志挖掘,再通过数据库自带的CDC组件或者配套同步软件去订阅日志。不同版本的菜单位置和存储过程名不完全一样,遇到问题第一时间查官方手册里的“日志归档配置”这一节,方向上不会错。
同步这块还有几个通用避坑点:给同步账号最小权限,不要顺手给超级管理员;binlog保留时长要能覆盖断点恢复的需求,尤其是有下游同步任务时;大事务会产生超大日志,CDC消费端的处理能力一定要跟上,否则延迟会越积越大。另外,很多人会问SQLite为什么那么受欢迎,原理很简单,它是单文件数据库,不需要独立服务,适合嵌入式场景和轻量工具,但并发写能力弱,不适合高并发多写场景。
3.4 数据库迁移不是“导出再导入”
“DBeaver如何进行数据库迁移”、“idea导出数据库脚本”这些搜索词很典型。很多人以为数据库迁移就是把旧库导出成SQL脚本,再到新库导入一遍就完事,实际执行起来全是坑。
第一个坑是字符集不一致,导出的时候库是latin1,导入到utf8mb4,中文直接乱码。第二个坑是方言语法差异,MySQL的自增列、序列、分区表语法在Oracle或PostgreSQL里不能直接用,需要逐个改。第三个坑是外键依赖顺序,导入时如果先建了子表,父表还没建,外键直接报错,稳妥做法是先禁用外键检查,导完再启用。第四个坑是类型映射,MySQL的tinyint(1)在Oracle里可能被映射成NUMBER,应用读到的是数字而不是布尔值,很容易埋雷。
我自己做迁移会用DBeaver的迁移功能,但不会完全依赖它,真正上线前一定会把目标端表结构单独核对一遍,检查默认值、自增序列、函数索引、分区策略这些GUI工具经常漏掉的细节。迁移完成后,除了看日志里有没有报错,还要做行数对比,再拿核心业务查询跑一遍回归。大表数据一定要分批处理,别让客户端一次拿几百万行,内存会爆。迁移前准备好回滚方案,这比任何文档都重要。
4. 热门技术点:向量、时序与国产数据库怎么学
4.1 向量数据库和普通数据库的根本区别
向量数据库这几年特别火,但很多人没搞明白它和MySQL、PostgreSQL这类传统数据库到底差在哪。最核心的区别是存储和检索的方式——普通数据库存的是结构化字段,支持等值查询和范围查询;向量数据库存的是嵌入向量,核心操作是“找最相似的向量”,也就是相似度搜索。
实现机制也不一样。传统关系型数据库用B+树组织索引,向量库常用HNSW或IVF这类近似最近邻索引。为什么会这样?因为“相似度搜索”这个操作放在B+树上很难高效完成,必须用专门为高维向量设计的图索引或倒排结构。实际应用场景包括语义检索和RAG,比如把文档切成片段,用embedding模型转成向量存进库,用户提问时同样把问题转成向量,去库里找topK最相近的文档片段,再送给大模型做回答。
学习向量库不要一上来就搞Milvus、Qdrant这种分布式方案,我建议先装一个pgvector,在PostgreSQL里体验向量索引和相似度查询,理解基本概念,然后再去对比专业向量数据库的优缺点。等你真正用起来,会发现过滤条件、数据量、召回率这些因素比“谁的速度快”更影响选型。
4.2 时序数据库表结构设计要点
搜索词里有一条“时序数据库的数据库结构怎样设计”,说明很多人遇到物联网或监控场景时,第一反应还是想把数据塞进MySQL。直接用MySQL存时序数据不是不行,但数据量一起来就非常难受:单表膨胀极快、时间范围查询慢、写入锁竞争大、存储成本高。
时序数据库的表结构设计和传统三范式设计完全是两套思路。核心维度有三个:标签、字段、时间戳。标签用来描述数据的业务维度,比如设备ID、地域、房间号,这些字段需要建索引,因为查询时基本都靠它们过滤;字段只放指标值,比如温度、CPU使用率、电压,这些不需要建索引,因为不做等值查询;时间戳是每张时序表都绕不开的核心列,类型要统一,是毫秒还是纳秒要从写入第一天就定下来。
还有两个容易忽略的设计点:一个是时间分区,数据按天或者按月自动分片,过期数据可以整区删除,不用一条条delete;另一个是降采样和保留策略,原始数据保留7天,1分钟聚合后的数据保留30天,这样存储和查询性能都能兼顾。你用这个思路去看某个时序数据库的建表DDL,基本都能在几分钟内看懂。
4.3 国产数据库学习路径与选型
从热词来看,达梦数据库安装教程、人大金仓数据库docker、nacos适配华为gaussdb、国产数据库排名前十名,都是高频搜索。这几年国产数据库逐渐被引入生产环境,作为一个开发,最好至少熟悉其中一两种。
我的学习建议是先用Docker把人大金仓或者达梦拉起来,跑通安装和建库,这一步能解决很多“装不上”的焦虑。接着做一次对照迁移实验,把之前写好的MySQL订单表DDL改一遍,记录差异点。最常见的是模式和序列的处理,还有分区语法、大小写敏感规则、自增列写法这些细节。再往后,用DBeaver或者Navicat连上去做增删改查、备份恢复,顺便试试JDBC驱动和连接池兼容性。像“nacos适配华为gaussdb”这类问题,本质就是驱动和方言适配,官方一般会提供兼容包或者补丁,照着配置文档走基本能解决。
看“国产数据库排名前十名”这种榜单,可以当线索,但不能当决策依据。真正选型要看几个硬指标:是否兼容你现有的开发框架、迁移工具链是否成熟、社区或者厂商支持力度怎么样。与其网上看排名,不如自己拿一个小业务系统做一次真实迁移测试,哪个坑少、文档全,心里就有数了。
4.4 数据库课程设计与面试如何结合
如果你正在做数据库课程设计,我建议把格局放大一点,不要只交一个能跑的增删改查。你有机会在项目里加入很多面试会问的东西:建表时主动给where条件列建索引;用事务模拟一次完整购买流程,余额不足直接回滚;做权限控制,区分管理员和普通用户;做备份和恢复演示;把慢查询优化写进报告,比如给order_time建索引前后,用EXPLAIN对比执行计划。
我在数据库学习笔记里专门整理过一份面试自查清单,和计算机三级数据库的考点重合度很高:三大范式、索引失效场景、事务隔离级别、MVCC原理、redo/undo/binlog的区别、主从复制的流程。每个词背后都值得写一篇详细笔记。面试时,只要能用自己的话把数据库工作原理讲清楚,比如“为什么redo log是物理日志,binlog是逻辑日志”,比背十条命令都有用。
5. 工具与选型:把学习效率提上去
5.1 “快速开发一套带数据库的软件用什么开发环境最好”
这是被问到最多的问题,但真没有标准答案,只有按场景给选择。个人博客、原型系统、单机工具,用SQLite加任意后端语言就够了,零安装、零运维;中小业务系统,从MySQL或PostgreSQL起步,预算允许直接用云数据库更省心;高并发互联网产品,MySQL加Redis,后期再加分库分表中间件;物联网监控场景,选时序数据库;语义检索和RAG,选向量数据库或者给PostgreSQL加pgvector。
如果你刚开始学,我建议从MySQL或PostgreSQL二选一。国内项目大概率用MySQL,生态和教程也最丰富;PostgreSQL在复杂查询、JSON支持、扩展能力上更强。不管选哪个,先把这个库用透,再横向扩展其他类型,不要一开始就堆一大堆组件。数据库就一个,先把核心用好,比什么都强。
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 原型/个人工具 | SQLite | 单文件零运维,嵌入应用即可 |
| 中小业务系统 | MySQL/PostgreSQL | 生态成熟、文档丰富、云数据库可用 |
| 高并发互联网 | MySQL + Redis | 读写分离、缓存加速、分库分表空间大 |
| 物联网监控 | 时序数据库 | 时间分区、降采样、高写入吞吐 |
| 语义检索/RAG | 向量数据库/pgvector | 高维相似度搜索,支持文本嵌入 |
5.2 MySQL与Oracle安装配置的琐碎坑
从热搜词看,mysql数据库下载、安装mysql数据库、mysql数据库修改结构、oracle数据库安装教程都是高频问题。MySQL安装本身不算难,真正坑的是初始化参数。举个例子,Linux环境下lower_case_table_names这个参数,如果要在不同操作系统间迁移,必须在初始化之前就确认好,不能随手改,否则表和库名的大小写行为会不一致。另一个高频坑是字符集,建库建表时一定要显式指定utf8mb4和配套的排序规则,不然后续和其他表做join时可能因为字符集不一致报错,也可能导致本可以走索引的查询失效。
Oracle安装数据库相对更重,从内存规划、字符集选择、监听端口到密码策略,每一步都可能卡住。单机学习直接用Docker或云上的免费资源会更省事。遇到安装问题不要盲目重装,先看日志文件里的具体报错,再改参数,改完重启验证。这个思路放之四海而皆准,比反复“下一步下一步”靠谱得多。
5.3 客户端工具与导出脚本的正确用法
日常管理数据库,DBeaver、Navicat、dbx这类工具都能帮上大忙,但工具有个共同的陷阱:导出脚本时默认按当前数据库方言生成,换一个数据库之后大概率跑不通。你拿MySQL导出的SQL脚本去PostgreSQL执行,自增列、反引号、类型定义全会出问题。
建议导出前先确认目标数据库类型,再按目标方言调整;大表数据别用GUI一把梭,用数据泵或者分批insert,减少内存占用和锁影响;导出脚本时要包含索引、约束、触发器、存储过程,而不只是表结构加数据。IDEA的数据库工具也一样,导出的脚本可以用来做版本管理和代码评审,但不能取代一次完整的迁移测试。
另外,搜索词里提到的“linux下的单文件数据库”,基本就是指SQLite。这类文件适合放在本地、同步盘或者容器里,轻量可靠,删除、备份都非常方便。但它不适合高并发多写场景,连接数一多就容易遇到数据库被锁的问题。
5.4 一条对我自己有效的学习路径复盘
如果让我重学一遍数据库,我会把时间花得更讲究。第一周先把MySQL的基础SQL和表结构设计过一遍,重点理解B+树索引和事务隔离,而不是急着写CRUD。第二周用一个小项目跑通增删改查、事务、索引优化,同时把连接池、备份恢复一起做掉。再往后,我会有意制造故障:让程序产生死锁、锁等待、慢查询,然后用系统表和错误日志去定位并解决,并把整个过程写进数据库学习笔记。这个阶段收获最大,因为故障越真实,你下次上手就越快。
之后再去横向扩展,接触PostgreSQL、SQLite、时序数据库、向量数据库,再挑达梦或者人大金仓其中一个国产库做一次迁移适配。面试前用真题查缺补漏,遇到不会的回来翻原理。数据库这门东西,真正难的从来不是语法,而是故障来临时能不能冷静定位。多看错误日志、多写实验、多复盘,比看十篇教程都管用。
