第 2 章 数据表的创建
从一个生活场景说起
假设你要管理一个班级的学生信息,最自然的做法是画一张 Excel 表格:
| 学号 | 姓名 | 年龄 |
|---|---|---|
| 1 | 张三 | 20 |
| 2 | 李四 | 22 |
在动手填数据之前,你得先做两件事:
- 设计表头:有哪几列?每列填什么类型的内容?学号能不能重复?姓名能不能留空?
- 把表格真正打印出来:光在脑子里设计没用,得落到纸面上。
数据库建表是同样的过程:
- "设计表头" = 在 Python 中用代码描述表结构(表名、列、类型、约束);
- "打印出来" = 让 SQLAlchemy 拿着这个设计去 MySQL 里执行
CREATE TABLE。
本章就是学习这两步。先记住一个对应关系:
| Excel 概念 | 数据库概念 |
|---|---|
| 一张工作表 | 一张表(Table) |
| 表头的一列 | 一个字段/列(Column) |
| 填的一行数据 | 一条记录(Record/Row) |
| 列的填写规则(必填、唯一) | 约束(Constraint) |
核心概念
两种定义表结构的风格
SQLAlchemy 提供两种风格定义表结构:
- Core 风格:用
Table对象直接描述表——贴近 SQL 本身,本章使用; - ORM 风格:用 Python 类(映射类)描述表——更面向对象,第 8 章起使用。
两种风格殊途同归,最终都是生成 CREATE TABLE 语句发给 MySQL。
三个关键角色
MetaData(元数据):可以理解为"设计图纸的档案柜"。你定义的所有表都登记在同一个 MetaData 里,它知道整个项目有哪些表。Table(表):一张表的设计图纸,包含表名和所有列。Column(列):图纸上的一栏,描述列名、数据类型、约束。
术语解释 —— 元数据(MetaData):"描述数据的数据"。表结构(有几列、什么类型)就是对数据的描述,所以叫元数据。
示例代码(逐行讲解)
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String
engine = create_engine(
"mysql+pymysql://root:123456@localhost:3306/demo_db?charset=utf8mb4",
echo=True,
)
metadata = MetaData() # ① 准备一个空档案柜
user_table = Table(
"user", # ② 表名(数据库里的名字)
metadata, # ③ 把这张表登记进档案柜
Column("id", Integer, primary_key=True, autoincrement=True),
Column("name", String(50), nullable=False),
Column("age", Integer),
)
# ④ 根据档案柜里登记的所有图纸,在数据库中真正建表
metadata.create_all(engine)三个列定义逐一拆解:
| 列定义 | 含义 |
|---|---|
Column("id", Integer, primary_key=True, autoincrement=True) | 整数类型的 id 列,是主键,值由数据库自动递增分配 |
Column("name", String(50), nullable=False) | 最长 50 字符的字符串列,不允许为空 |
Column("age", Integer) | 整数列,允许为空(默认) |
术语解释 —— 主键(Primary Key):一张表里每行数据的"身份证号",用于唯一标识一行记录。主键不能重复、不能为 NULL。
autoincrement=True表示让数据库自动编号(1、2、3……),对应 MySQL 的AUTO_INCREMENT。
执行后 MySQL 中实际执行的 SQL 大致为:
CREATE TABLE user (
id INTEGER NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
age INTEGER,
PRIMARY KEY (id)
)打开 echo=True,你就能在控制台亲眼看到这条 SQL。
关键知识点
| 概念 | 说明 |
|---|---|
MetaData | 表结构的"登记册",集中存放所有 Table 定义 |
Table | 描述一张表:表名 + 列集合 |
Column | 描述一列:类型、主键、是否为空、默认值 |
create_all() | 把登记册里的表在数据库中创建出来(已存在则跳过) |
常用列类型速查:
| SQLAlchemy 类型 | MySQL 实际类型 | 适合存放 |
|---|---|---|
Integer | INTEGER | 整数:年龄、数量 |
String(n) | VARCHAR(n) | 短文本:姓名、标题 |
Text | TEXT | 长文本:文章正文 |
Float | FLOAT | 小数(精度要求不高) |
Numeric(10, 2) | DECIMAL(10,2) | 金额(精确小数) |
Boolean | TINYINT(1) | 是/否 |
DateTime | DATETIME | 日期时间 |
经验之谈:存金额永远不要用
Float!浮点数有精度误差(0.1 + 0.2 ≠ 0.3),要用Numeric(对应 MySQL 的 DECIMAL)。
深入理解:create_all 到底做了什么?
metadata.create_all(engine) 的工作流程:
- 遍历档案柜里登记的所有 Table;
- 对每张表,先问 MySQL:"这张表已经存在了吗?"(执行
SHOW TABLES类似的检查); - 不存在 → 生成并执行
CREATE TABLE; - 已存在 → 直接跳过,什么都不做。
所以重复运行建表脚本是安全的,不会报错,也不会清空你的数据。
进阶内容
1. create_all 的局限——它"只增不改"。 如果你在 user_table 里加了一列再运行 create_all(),已存在的表不会被修改。数据库结构的变更需要专门的迁移工具 Alembic(SQLAlchemy 官方出品),它像 Git 管理代码版本一样管理表结构版本。第 17 章的最佳实践会再次提到它。
2. 常用列约束一览。
Column("email", String(100), unique=True) # 唯一约束:不允许重复
Column("status", Integer, default=1) # 默认值:不填时自动为 1
Column("name", String(50), index=True) # 建索引:加速按该列查询
Column("remark", String(200), nullable=True) # 允许为空(默认行为)3. 查看已有表的结构。 可以用 MySQL 命令行执行 DESC user; 或 SHOW CREATE TABLE user;,对照检查 SQLAlchemy 生成的表是否符合预期。
注意事项
create_all()是"只增不改"的:如果表已存在,它不会帮你修改表结构。改结构要用 Alembic 迁移工具。- 常用列类型:
Integer、String(n)、Text、Float、Boolean、DateTime。 - MySQL 中字符串必须指定长度,即
String(50),不能写成裸String。 - 主键建议用
Integer, primary_key=True, autoincrement=True(对应 MySQL 的AUTO_INCREMENT)。 - MySQL 的表默认引擎通常是 InnoDB,支持事务和外键,无需额外指定。
- 表名和列名建议用小写字母 + 下划线(如
user_name),避免使用 MySQL 保留字(如order、desc)作为名字。
本章小结
- 建表两步走:代码里描述表结构 →
create_all()在数据库中真正建表; - 三个角色:MetaData(档案柜)、Table(图纸)、Column(一栏);
- 主键是每行的"身份证号",
autoincrement让数据库自动编号; create_all()只增不改,改表结构要靠 Alembic。