编辑模式 · 点击文字即可修改 · Ctrl+S 导出 再次按 E 或点击左上角退出
模块 6.4 · SQL 与 SQLite

数据库正传

看数据库如何又快又好地把数据存好、查快

上一节的文件方案,埋了三个问题

文件存储系统的三个问题

🐢
效率
存一条
要重写整份文件
💥
并发
多人同时
脏写 / 脏读
🧨
健壮
写一半崩了
一坏全坏
存储,是普世的需求
这些,又是人人都会遇到的问题
1
一条经验

七步之内,必有解药

在计算机领域,只要是人人都可能遇到的问题,就一定有人早已做好了专门的东西——这一次,它就是数据库

关系型
非关系型
服务式
嵌入式
较少见
MySQL
MySQL
PostgreSQL
PostgreSQL
ClickHouse
ClickHouse
MariaDB
MariaDB
MongoDB
MongoDB
Redis
Redis
SQLite
SQLite
DuckDB
DuckDB
1
维度① 数据模型 · 先看数据长什么样

数据的三种形态

结构化
商品125
🔗
商品12
整整齐齐的表,还能互相关联
半结构化
{ "name": "茶", "price": 12 }
{ "name": "笔", "tag": "文具" }
有结构但松散,像 JSON,字段可以对不齐
非结构化
🖼 🎬 📄
图片、视频、大段文本
维度① 数据模型 · 配什么库

关系型 vs 非关系型

关系型(大多数)
数据存成一张张规整的表,用一种叫 SQL 的通用语言读写。MySQL / PostgreSQL / SQLite 都是。
非关系型 · NoSQL
要求更松散。典型是文档型的 MongoDB,存进去的东西长得就像 JSON

对象存储(图片、视频)也算非关系型,但一般不叫它数据库,就叫“对象存储”。

维度② 服务方式 · 怎么连上它

嵌入式 vs 服务式

🖥 服务式 · 连它要一串配置
host     db.example.com:5432
database mydb
user     admin
password ••••••••
常驻进程、守着端口,多应用 · 并发 · 权限——生产主流
📦 嵌入式 · 连它只要一个文件
test.db
不启动、不占端口。SQLite 就是——import sqlite3 打开这个文件即用。

你手机里此刻就躺着几十个 SQLite——微信聊天记录、浏览器历史,底下都是它。

关系型
非关系型
服务式
嵌入式
较少见
MySQL
MySQL
PostgreSQL
PostgreSQL
ClickHouse
ClickHouse
MariaDB
MariaDB
MongoDB
MongoDB
Redis
Redis
SQLite
SQLite
DuckDB
DuckDB
12
关系型数据库的通用语言

常用的四句 SQL

sql
CREATE TABLE 表名 (字段名 字段类型, 字段名 字段类型, ……)-- 建一张表
INSERT INTO 表名 (字段, 字段, ……) VALUES (, , ……)-- 插入一行
SELECT 字段, 字段 FROM 表名 WHERE 条件-- 按条件查询
DELETE FROM 表名 WHERE 条件-- 按条件删除
建表时,先定好每列的类型

不同的数据库支持的数据类型不同

类别MySQLPostgreSQLSQLite
整数INT / BIGINTINTEGER / BIGINTINTEGER
小数DECIMAL / FLOATNUMERIC / REALREAL
文本VARCHAR / TEXTVARCHAR / TEXTTEXT
日期时间DATE / DATETIMEDATE / TIMESTAMP无 · 用 TEXT 存
布尔TINYINT(1)BOOLEAN无 · 用 0/1
建一张电影表 films

用 CREATE TABLE 创建一个表

sqlpython
CREATE TABLE 表名 (字段名 字段类型, 字段名 字段类型, ……)
CREATE TABLE films (title TEXT, language TEXT, release_date TEXT, created_at TEXT)
CREATE TABLE films (id INTEGER, title TEXT, language TEXT, release_date TEXT, created_at TEXT)
CREATE TABLE films (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, language TEXT, release_date TEXT, created_at TEXT)
cur.execute("CREATE TABLE films (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, language TEXT, release_date TEXT, created_at TEXT)")
cur.execute(""" CREATE TABLE films ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, language TEXT, release_date TEXT, created_at TEXT ) """)
films
id titlelanguagerelease_datecreated_at 字段名字段名
       
       
       
12345
往 films 里插一条

用 INSERT 插入数据

sqlpython
INSERT INTO 表名 (字段, 字段, ……) VALUES (, , ……)
cur.execute(""" INSERT INTO films (title, language, release_date, created_at) VALUES ('肖申克的救赎', '英语', '1994-09-23', datetime('now')) """) conn.commit()
films
idtitlelanguagerelease_datecreated_at
1肖申克的救赎英语1994-09-232026-…
     
     
1
把数据查出来

用 SELECT 查询数据

sqlpython
SELECT 字段名1, 字段名2 FROM 表名 WHERE 条件表达式
SELECT * FROM films WHERE language = '日语'
SELECT * FROM films
cur.execute("SELECT * FROM films").fetchall()
查询结果
idtitlelanguagerelease_datecreated_at
1肖申克的救赎英语1994-09-232026-…
     
     
123
删一条记录

用 DELETE 删除数据

python
cur.execute("DELETE FROM films WHERE id = 2")
conn.commit()
再认两个,不演示

UPDATE 与 DROP

sql
UPDATE 表名 SET 字段名 = WHERE 条件表达式
DROP TABLE 表名
🔎 搜索 奥德赛 花样年华 欢迎来龙餐馆 ' OR '1'='1
python
cur.execute("SELECT * FROM films WHERE title = '肖申克的救赎奥德赛花样年华欢迎来龙餐馆' OR '1'='1'")
cur.execute("SELECT * FROM films WHERE title = ?", ["值"])
123456789
数据量大了,才见真章

三个关键字:排序 · 逆序 · 限量

ORDER BY
按某列排序
(默认从小到大)
DESC
加在后面
改成从大到小
LIMIT n
只取前 n 条
SELECT * FROM films ORDER BY created_at DESC LIMIT 5 -- 最近入库 5 条
高光时刻

一句,顶三行

python · 6.3 文件版
records = load_history() # 全读进内存
records.reverse() # 自己倒序
return records[:10] # 自己切片
sql · 数据库版
SELECT * FROM history ORDER BY created_at DESC LIMIT 10
第一招
ORDER BY created_at DESC LIMIT 10
内存
哪个更大?
硬盘 · 全表
123456
第二招
输出
内存 · 大小固定
段③段⑦
段⑦段①
段①段⑤
段⑤段②
段②段③
段⑧段⑧
段④段④
段⑥段⑥
硬盘 · 临时文件(有序段)
硬盘 · 全表(无序)
12345678
第三招
ORDER BY created_at DESC LIMIT 10
内存
硬盘
索引 · 按 created_at 排好序
全表 · 无序
1234
这一节,理清两件事

为什么会需要数据库
数据库长什么样