5.1 数据库基础


5.1 数据库基础

本节摘要:数据库不是"更高级的文件",而是自带并发管控、数据约束、事务保证和索引加速的专业存储系统。本节先讲清 JSON 文件在多用户场景下必然翻车的四个原因,再认识关系型数据库的核心概念——表、行、列、主键、外键,用 SQLite 亲手建出轻记账的第一张表并写几条 SQL。概念落到手上,下一节的 ORM 才不会变成黑话。

本节要解决什么

阅读完本节,你应当能够:

  1. 说出文件存储在并发、约束、事务、索引四个维度上的短板
  2. 理解表、行、列、主键、外键五个基本概念
  3. 用 sqlite3 模块建表、插入、查询轻记账数据
  4. 区分关系型与非关系型数据库的适用场景
  5. 解释事务的原子性及其对记账操作的意义

为什么 JSON 文件撑不住了

复盘一个真实的翻车现场:两人同时用轻记账,甲记一笔"午饭 20",乙记一笔"电影 40"。程序各自的流程都是"读文件到内存 → 追加 → 整体写回"。时序稍不巧:乙在甲"读之后写之前"也完成了读——两人都在旧数据上追加,后写回的把先写回的整个覆盖,甲那笔账人间蒸发。这不是粗心,是文件存储的结构性缺陷:

  • 并发无管控:多个进程同时读写,谁都不知道对方的存在
  • 无约束:金额存成字符串、日期格式五花八门,文件一概照收,垃圾进垃圾出
  • 无事务:记一笔要改两处数据,改到一半程序崩了,文件处于半新半旧的矛盾状态
  • 无索引:几千笔账里查某个月,只能从头到尾扫一遍

图 5-1 文件存储与数据库的能力对比

图 5-1 文件存储与数据库的能力对比

关系型数据库:表格的艺术

关系型数据库把数据组织成一张张二维表。轻记账的账目表长这样:

id item amount date user_id
1 早饭 12.5 2026-09-01 1
2 地铁 4.0 2026-09-01 1
3 电影 40.0 2026-09-02 2

五个概念各就各位:是字段,建表时声明类型;是一条记录;主键是每行的身份证号(id),绝不重复;外键是表与表的纽带——user_id 指向用户表的 id,声明"这笔账属于谁",数据库负责检查指向的人真的存在;查询语言 SQL 是通用操作语言,增删改查各一个动词。

SQLite:一个文件就是一个数据库

生产级数据库(PostgreSQL、MySQL)要安装服务、配置账号;SQLite 是"嵌进程序里的数据库",整个库就是一个文件,Python 标准库自带驱动,零配置——学习与中小项目的事实标准:

import sqlite3 # 连接(文件不存在会自动创建) conn = sqlite3.connect("ledger.db") cur = conn.cursor() # 建表:类型与约束在这里立规矩 cur.execute(""" CREATE TABLE IF NOT EXISTS records ( id INTEGER PRIMARY KEY AUTOINCREMENT, item TEXT NOT NULL, amount REAL NOT NULL CHECK (amount > 0), date TEXT NOT NULL, user_id INTEGER NOT NULL REFERENCES users(id) ) """) # 插入:问号占位符传参,杜绝拼接注入 cur.execute( "INSERT INTO records (item, amount, date, user_id) VALUES (?, ?, ?, ?)", ("午饭", 20.0, "2026-09-02", 1), ) conn.commit() # 提交事务 # 查询:九月的账目,按日期倒序 rows = cur.execute( "SELECT item, amount FROM records WHERE date LIKE ? ORDER BY date DESC", ("2026-09%",), ).fetchall() print(rows) # [('午饭', 20.0)] conn.close()

三处工程要点:占位符问号由驱动安全传参,绝不用字符串拼接拼 SQL——那是注入攻击的头号入口,6.6 安全节展开;commit 与 execute 分离,你不提交就不落盘,事务的原子性正来自于此——同一事务里多条语句要么全部生效要么全部作废,"记账成功但统计没更新"的矛盾数据从根上杜绝;CHECK 约束让负数金额在数据库层面就进不了门,与应用层校验构成双保险。

关系型还是非关系型

主流分两大阵营:关系型(表、SQL、强约束)与非关系型(文档、键值、灵活结构)。选型一句话版:数据有清晰关系、要一致性保证,选关系型——账目、订单、用户,几乎一切业务系统的主干;结构多变、追求极致读写吞吐,考虑非关系型——缓存(第 6 章会用的 Redis 就是键值型)、日志、消息队列。轻记账毫无疑问用关系型,但你的工具箱里该知道还有另一格抽屉。

演练与变式

给 SQLite 库补一张 users 表(id、username 两列即可),插入两个用户,把上面的账目分别挂到两人名下,再用 JOIN 查询"每个用户各自的合计":

rows = cur.execute(""" SELECT u.username, SUM(r.amount) FROM users u JOIN records r ON r.user_id = u.id GROUP BY u.username """).fetchall() print(rows) # [('甲', 16.5), ('乙', 40.0)]

变式一:往表里插入一条负数金额,观察 CHECK 约束怎样当场拦截。变式二:连续插入两条不 commit 就 close,重开库验证数据没有进去——亲手确认"不提交不落盘"。

易错点清单

  • 忘记 commit:程序不报错,数据却没保存,重启后一脸茫然
  • 字符串拼接 SQL:注入攻击开门揖盗,占位符是唯一正解
  • 主键当业务数据用:id 只做标识,别用它表达含义,重排与迁移都会痛
  • 一库多程序直接开文件:SQLite 并发能力有限,多进程高频写要上专业数据库

本节要点回顾

  • 文件存储四短板:并发、约束、事务、索引,数据库各给一套保证
  • 五概念:表、行、列、主键、外键,SQL 四动词增删改查
  • SQLite 零配置上手,占位符传参防注入,commit 决定事务生效
  • CHECK 与 NOT NULL 是双保险的底层,垃圾数据进不了门
  • 关系型管主干业务,非关系型补缓存与高吞吐场景

表建好了、SQL 会写了,但业务代码里到处写 SQL 字符串很快会烦。下一节 ORM,用对象说话。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U