入门 SQLAlchemy 教程:从 0 到 1 创建数据库
-
- 🚀核心目标
- 一、前置准备(必做)
- [二、核心概念:3 分钟搞懂 ORM](#二、核心概念:3 分钟搞懂 ORM)
-
- [💡 什么是 SQLAlchemy?](#💡 什么是 SQLAlchemy?)
- [三、实操:从 0 创建数据库与表](#三、实操:从 0 创建数据库与表)
-
- [1. 初始化连接与配置](#1. 初始化连接与配置)
- [2. 定义表结构:User(用户表)](#2. 定义表结构:User(用户表))
-
- [User 表结构 ER 片段](#User 表结构 ER 片段)
- [3. 定义表结构:Post(帖子表)](#3. 定义表结构:Post(帖子表))
-
- [User-Post 一对多关系 ER 图](#User-Post 一对多关系 ER 图)
- [4. 扩展:多对多关系(User-Tag)](#4. 扩展:多对多关系(User-Tag))
-
- [User-Tag 多对多关系 ER 图](#User-Tag 多对多关系 ER 图)
- [5. 扩展:一对一关系(User-UserProfile)](#5. 扩展:一对一关系(User-UserProfile))
-
- [User-UserProfile 一对一关系 ER 图](#User-UserProfile 一对一关系 ER 图)
- [6. 生成数据库与表](#6. 生成数据库与表)
- 四、核心操作:增删改查(完整可运行)
-
- [1. 新增数据](#1. 新增数据)
- [2. 查询数据](#2. 查询数据)
- [3. 修改数据](#3. 修改数据)
- [4. 删除数据](#4. 删除数据)
- 五、完整代码汇总(可直接复制运行)
- 六、避坑指南(高频问题解决)
- 七、总结
- 八、引用
作者:高玉涵
时间:2026.4.13 15:36
博客: blog.csdn.net/cg_i
环境:Windows11、Python 3.12
🚀核心目标
本教程专为初学者设计,避开晦涩术语,通过「用户 (User)- 帖子 (Post)」案例,带你掌握:
- 使用 SQLAlchemy (ORM) 操作数据库,告别原生 SQL。
- 理解表与表之间的核心关系(一对多、多对多、一对一)。
- 完成一个可直接运行、控制台交互的完整项目。
一、前置准备(必做)
在开始之前,请确保你的开发环境满足以下要求:
-
安装 Python:建议安装 Python 3.7+(推荐最新稳定版)。
-
安装核心库:打开终端 (CMD/PowerShell),执行以下命令:
bashpip install sqlalchemy
二、核心概念:3 分钟搞懂 ORM
用生活场景类比数据库核心概念,快速理解:
| 生活类比 | 数据库术语 | 说明 |
|---|---|---|
| 文件夹 | Database (数据库) | 存放所有数据的容器(例如:myblog.db)。 |
| Excel 表格 | Table (表) | 数据库中的具体表格(例如:user 表存用户,post 表存帖子)。 |
| 表头 (列) | Column (字段) | 表格的列定义(例如:id, username, title)。 |
| 表间连线 | Relationship (关系) | 表格之间的关联(例如:一个用户对应多篇帖子)。 |
💡 什么是 SQLAlchemy?
SQLAlchemy 是 Python 生态中主流的数据库 ORM 工具,核心作用是将 Python 代码与数据库操作解耦:
- 无 SQLAlchemy:需手写原生 SQL(如
select * from user where id=1); - 有 SQLAlchemy:仅需操作 Python 类 / 对象,自动映射为数据库表 / 数据。
ORM (对象关系映射) 核心对应关系:
- Python 类 → 数据库表(如
class User对应user表); - Python 对象 → 表的一行数据(如
user = User(name="张三")对应表中一行)。
三、实操:从 0 创建数据库与表
1. 初始化连接与配置
新建 db_demo.py 文件,写入以下代码(核心是配置数据库连接、初始化核心组件):
python
# 1. 导入核心依赖
from sqlalchemy import create_engine, Column, Integer, String, Text, ForeignKey, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship, backref
# 2. 配置数据库连接(SQLite 无需额外安装,文件形式存储)
# echo=False:关闭原生 SQL 打印;echo=True 可调试 SQL 语句
engine = create_engine('sqlite:///myblog.db', echo=False)
# 3. 初始化表基类(所有表的类必须继承此基类,固定写法)
Base = declarative_base()
# 4. 创建数据库会话(增删改查的核心工具)
Session = sessionmaker(bind=engine)
session = Session() # 实例化会话,后续操作均依赖此对象
2. 定义表结构:User(用户表)
User 类映射数据库 user 表,包含基础字段 + 与 Post 表的关联关系:
python
# 定义 User 表(对应数据库 user 表)
class User(Base):
__tablename__ = 'user' # 数据库表名(小写规范)
# 字段定义
id = Column(Integer, primary_key=True, comment="用户唯一标识") # 主键
username = Column(String(50), unique=True, nullable=False, comment="用户名,唯一不可重复")
password = Column(String(100), nullable=False, comment="密码(建议加密存储)")
email = Column(String(100), nullable=True, comment="用户邮箱,可选字段")
# 关系定义:一对多关联 Post 表
# backref='author':给 Post 表自动添加 author 属性,反向关联 User
# lazy='dynamic':延迟加载,返回查询器而非直接加载所有数据,优化性能
posts = relationship('Post', backref='author', lazy='dynamic')
# 自定义打印格式,控制台输出更友好
def __repr__(self):
return f"User(用户名='{self.username}', 邮箱='{self.email}')"
User 表结构 ER 片段
User
Integer
id
PK
主键
String(50)
username
UK
用户名(唯一)
String(100)
password
密码
String(100)
email
邮箱(可选)
3. 定义表结构:Post(帖子表)
Post 表通过外键 关联 User 表,实现 "帖子归属用户" 的核心逻辑:
python
# 定义 Post 表(对应数据库 post 表)
class Post(Base):
__tablename__ = 'post' # 数据库表名
# 字段定义
id = Column(Integer, primary_key=True, comment="帖子唯一标识")
title = Column(String(100), nullable=False, comment="帖子标题")
content = Column(Text, nullable=False, comment="帖子正文(长文本)")
# 外键:关联 User 表的 id 字段,实现"帖子归属用户"
user_id = Column(Integer, ForeignKey('user.id'), nullable=False, comment="关联用户ID")
# 自定义打印格式
def __repr__(self):
return f"Post(标题='{self.title}')"
User-Post 一对多关系 ER 图
发布
User
Integer
id
PK
主键
String(50)
username
UK
用户名
String(100)
password
密码
String(100)
email
邮箱
Post
Integer
id
PK
主键
String(100)
title
帖子标题
Text
content
帖子正文
Integer
user_id
FK
关联用户ID
4. 扩展:多对多关系(User-Tag)
新增「标签表」+「用户 - 标签关联表」,实现 "一个用户关注多个标签,一个标签被多个用户关注":
python
# 多对多关联表(无主键,仅存储两个表的关联关系)
user_tag = Table(
'user_tag', # 关联表名
Base.metadata,
Column('user_id', Integer, ForeignKey('user.id'), comment="关联用户ID"),
Column('tag_id', Integer, ForeignKey('tag.id'), comment="关联标签ID")
)
# 标签表
class Tag(Base):
__tablename__ = 'tag'
id = Column(Integer, primary_key=True, comment="标签唯一标识")
name = Column(String(50), unique=True, nullable=False, comment="标签名称(唯一)")
# 多对多关联 User 表
# secondary:指定关联表;backref:反向关联,User 表可通过 tags 属性访问标签
users = relationship('User', secondary=user_tag, backref='tags', lazy='dynamic')
def __repr__(self):
return f"Tag(名称='{self.name}')"
User-Tag 多对多关系 ER 图
包含
包含
User
Integer
id
PK
主键
String(50)
username
UK
用户名
String(100)
password
密码
String(100)
email
邮箱
Tag
Integer
id
PK
主键
String(50)
name
UK
标签名称
user_tag
Integer
user_id
FK
外键 -> User.id
Integer
tag_id
FK
外键 -> Tag.id
5. 扩展:一对一关系(User-UserProfile)
新增「用户资料表」,实现 "一个用户仅对应一份详细资料":
python
# 用户资料表(一对一关联 User)
class UserProfile(Base):
__tablename__ = 'user_profile'
id = Column(Integer, primary_key=True, comment="资料唯一标识")
age = Column(Integer, nullable=True, comment="用户年龄")
address = Column(String(200), nullable=True, comment="用户地址")
# 外键 + unique=True:确保一个用户仅对应一份资料
user_id = Column(Integer, ForeignKey('user.id'), unique=True, nullable=False, comment="关联用户ID")
# 一对一关系配置:uselist=False 表示非列表(仅单个对象)
user = relationship('User', backref=backref('profile', uselist=False))
def __repr__(self):
return f"UserProfile(年龄={self.age}, 地址='{self.address}')"
User-UserProfile 一对一关系 ER 图
拥有
User
Integer
id
PK
主键
String(50)
username
UK
用户名
String(100)
password
密码
String(100)
email
邮箱
UserProfile
Integer
id
PK
主键
Integer
age
年龄
String(200)
address
地址
Integer
user_id
FK
关联用户ID(唯一)
6. 生成数据库与表
执行以下代码,自动创建所有定义的表(仅需执行一次):
python
# 创建所有继承 Base 的表(User/Post/Tag/UserProfile)
Base.metadata.create_all(engine)
print("✅ 数据库和表创建成功!可在当前文件夹查看 myblog.db 文件")
四、核心操作:增删改查(完整可运行)
在 db_demo.py 末尾添加以下代码,验证表关系与数据操作:
1. 新增数据
python
# ========== 新增数据 ==========
try:
# 1. 创建用户
user1 = User(username="老程", password="123456", email="laocheng@163.com")
session.add(user1)
session.commit() # 提交会话,写入数据库
# 2. 给用户添加帖子(通过 backref='author' 关联)
post1 = Post(title="SQLAlchemy 入门教程", content="ORM 操作数据库超简单!", author=user1)
post2 = Post(title="Python 数据库最佳实践", content="避坑指南:外键与关系配置", author=user1)
session.add_all([post1, post2])
session.commit()
# 3. 给用户添加标签(多对多)
tag1 = Tag(name="Python")
tag2 = Tag(name="SQLAlchemy")
user1.tags.append(tag1)
user1.tags.append(tag2)
session.add_all([tag1, tag2])
session.commit()
# 4. 给用户添加个人资料(一对一)
profile1 = UserProfile(age=28, address="北京市朝阳区", user=user1)
session.add(profile1)
session.commit()
print("\n=== 📌 新增数据成功 ===")
print(f"用户:{user1}")
print(f"帖子:{post1}, {post2}")
print(f"标签:{tag1}, {tag2}")
print(f"资料:{profile1}")
except Exception as e:
session.rollback() # 出错回滚,避免数据异常
print(f"❌ 新增数据失败:{e}")
2. 查询数据
python
# ========== 查询数据 ==========
print("\n=== 🔍 查询数据结果 ===")
# 1. 查询所有用户
all_users = session.query(User).all()
print(f"所有用户:{all_users}")
# 2. 通过 ID 查询单个用户
user = session.query(User).get(1)
if user:
print(f"\n单个用户(ID=1):{user}")
# 查询用户的所有帖子
user_posts = user.posts.all()
print(f"该用户的帖子:{[p.title for p in user_posts]}")
# 查询用户的所有标签
user_tags = user.tags.all()
print(f"该用户的标签:{[t.name for t in user_tags]}")
# 查询用户的个人资料
print(f"该用户的资料:{user.profile}")
# 3. 通过帖子查询作者(反向关联)
post = session.query(Post).get(1)
if post:
print(f"\n帖子(ID=1)的作者:{post.author.username}")
3. 修改数据
python
# ========== 修改数据 ==========
print("\n=== ✏️ 修改数据操作 ===")
user = session.query(User).get(1)
if user:
old_username = user.username
user.username = "程序员老程" # 修改用户名
user.profile.age = 29 # 修改用户资料
session.commit()
print(f"修改前用户名:{old_username} → 修改后:{user.username}")
print(f"修改后年龄:{user.profile.age}")
4. 删除数据
python
# ========== 删除数据 ==========
print("\n=== 🗑️ 删除数据操作 ===")
post = session.query(Post).get(2)
if post:
post_title = post.title
session.delete(post) # 删除帖子
session.commit()
# 验证删除结果
user = session.query(User).get(1)
remaining_posts = [p.title for p in user.posts.all()]
print(f"删除的帖子:{post_title}")
print(f"用户剩余帖子:{remaining_posts}")
# 关闭会话,释放资源
session.close()
五、完整代码汇总(可直接复制运行)
python
# 导入核心依赖
from sqlalchemy import create_engine, Column, Integer, String, Text, ForeignKey, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship, backref
# 1. 配置数据库连接
engine = create_engine('sqlite:///myblog.db', echo=False)
Base = declarative_base()
Session = sessionmaker(bind=engine)
session = Session()
# 2. 定义表结构
# 2.1 User 表(用户)
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True, comment="用户唯一标识")
username = Column(String(50), unique=True, nullable=False, comment="用户名(唯一)")
password = Column(String(100), nullable=False, comment="密码(加密存储)")
email = Column(String(100), nullable=True, comment="用户邮箱")
posts = relationship('Post', backref='author', lazy='dynamic') # 一对多关联 Post
def __repr__(self):
return f"User(用户名='{self.username}', 邮箱='{self.email}')"
# 2.2 Post 表(帖子)
class Post(Base):
__tablename__ = 'post'
id = Column(Integer, primary_key=True, comment="帖子唯一标识")
title = Column(String(100), nullable=False, comment="帖子标题")
content = Column(Text, nullable=False, comment="帖子正文")
user_id = Column(Integer, ForeignKey('user.id'), nullable=False, comment="关联用户ID")
def __repr__(self):
return f"Post(标题='{self.title}')"
# 2.3 多对多关联表(用户-标签)
user_tag = Table(
'user_tag',
Base.metadata,
Column('user_id', Integer, ForeignKey('user.id'), comment="关联用户ID"),
Column('tag_id', Integer, ForeignKey('tag.id'), comment="关联标签ID")
)
# 2.4 Tag 表(标签)
class Tag(Base):
__tablename__ = 'tag'
id = Column(Integer, primary_key=True, comment="标签唯一标识")
name = Column(String(50), unique=True, nullable=False, comment="标签名称(唯一)")
users = relationship('User', secondary=user_tag, backref='tags', lazy='dynamic')
def __repr__(self):
return f"Tag(名称='{self.name}')"
# 2.5 UserProfile 表(用户资料,一对一)
class UserProfile(Base):
__tablename__ = 'user_profile'
id = Column(Integer, primary_key=True, comment="资料唯一标识")
age = Column(Integer, nullable=True, comment="用户年龄")
address = Column(String(200), nullable=True, comment="用户地址")
user_id = Column(Integer, ForeignKey('user.id'), unique=True, nullable=False, comment="关联用户ID")
user = relationship('User', backref=backref('profile', uselist=False))
def __repr__(self):
return f"UserProfile(年龄={self.age}, 地址='{self.address}')"
# 3. 创建数据库和表
Base.metadata.create_all(engine)
print("✅ 数据库和表创建成功!")
# 4. 增删改查操作
try:
# 新增数据
user1 = User(username="老程", password="123456", email="laocheng@163.com")
session.add(user1)
session.commit()
post1 = Post(title="SQLAlchemy 入门教程", content="ORM 操作数据库超简单!", author=user1)
post2 = Post(title="Python 数据库最佳实践", content="避坑指南:外键与关系配置", author=user1)
session.add_all([post1, post2])
session.commit()
tag1 = Tag(name="Python")
tag2 = Tag(name="SQLAlchemy")
user1.tags.append(tag1)
user1.tags.append(tag2)
session.add_all([tag1, tag2])
session.commit()
profile1 = UserProfile(age=28, address="北京市朝阳区", user=user1)
session.add(profile1)
session.commit()
print("\n=== 📌 新增数据成功 ===")
print(f"用户:{user1}")
print(f"帖子:{post1}, {post2}")
print(f"标签:{tag1}, {tag2}")
print(f"资料:{profile1}")
# 查询数据
print("\n=== 🔍 查询数据结果 ===")
all_users = session.query(User).all()
print(f"所有用户:{all_users}")
user = session.query(User).get(1)
if user:
print(f"\n单个用户(ID=1):{user}")
print(f"该用户的帖子:{[p.title for p in user.posts.all()]}")
print(f"该用户的标签:{[t.name for t in user.tags.all()]}")
print(f"该用户的资料:{user.profile}")
post = session.query(Post).get(1)
if post:
print(f"\n帖子(ID=1)的作者:{post.author.username}")
# 修改数据
print("\n=== ✏️ 修改数据操作 ===")
user = session.query(User).get(1)
if user:
old_username = user.username
user.username = "程序员老程"
user.profile.age = 29
session.commit()
print(f"修改前用户名:{old_username} → 修改后:{user.username}")
print(f"修改后年龄:{user.profile.age}")
# 删除数据
print("\n=== 🗑️ 删除数据操作 ===")
post = session.query(Post).get(2)
if post:
post_title = post.title
session.delete(post)
session.commit()
user = session.query(User).get(1)
remaining_posts = [p.title for p in user.posts.all()]
print(f"删除的帖子:{post_title}")
print(f"用户剩余帖子:{remaining_posts}")
except Exception as e:
session.rollback()
print(f"❌ 操作失败:{e}")
finally:
session.close()
print("\n✅ 会话已关闭,操作完成!")
六、避坑指南(高频问题解决)
- 报错
no such table: user:- 原因:未执行
Base.metadata.create_all(engine),或代码顺序错误(先增删改查、后创建表); - 解决:确保
create_all在所有数据操作前执行,删除旧的myblog.db重新运行。
- 原因:未执行
- 报错
unique constraint failed: user.username:- 原因:
username字段设置了unique=True,重复插入相同用户名; - 解决:更换用户名,或先查询是否已存在该用户。
- 原因:
- 查询不到数据 :
- 原因:未执行
session.commit(),数据仅存于会话、未写入数据库; - 解决:所有新增 / 修改操作后必须调用
session.commit()。
- 原因:未执行
- 多对多关联表报错 :
- 原因:原代码中误将
Table写成Column(已修正); - 解决:关联表需用
sqlalchemy.Table定义,而非Column。
- 原因:原代码中误将
- 一对一关系数据重复 :
- 原因:未给
user_id设置unique=True; - 解决:确保
user_id = Column(..., unique=True),限制一个用户仅对应一份资料。
- 原因:未给
七、总结
本教程完成了以下核心内容:
- 理解 ORM 核心概念,掌握 SQLAlchemy 基础配置;
- 定义 4 张核心表(User/Post/Tag/UserProfile),覆盖一对多、多对多、一对一 3 种核心关系;
- 实现完整的增删改查操作,结合 ER 图直观理解表关系;
- 解决高频报错问题,确保代码可直接运行。