入门 SQLAlchemy 教程:从 0 到 1 创建数据库

入门 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)」案例,带你掌握:

  1. 使用 SQLAlchemy (ORM) 操作数据库,告别原生 SQL。
  2. 理解表与表之间的核心关系(一对多、多对多、一对一)。
  3. 完成一个可直接运行、控制台交互的完整项目。

一、前置准备(必做)

在开始之前,请确保你的开发环境满足以下要求:

  • 安装 Python:建议安装 Python 3.7+(推荐最新稳定版)。

  • 安装核心库:打开终端 (CMD/PowerShell),执行以下命令:

    bash 复制代码
    pip 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✅ 会话已关闭,操作完成!")

六、避坑指南(高频问题解决)

  1. 报错 no such table: user
    • 原因:未执行 Base.metadata.create_all(engine),或代码顺序错误(先增删改查、后创建表);
    • 解决:确保 create_all 在所有数据操作前执行,删除旧的 myblog.db 重新运行。
  2. 报错 unique constraint failed: user.username
    • 原因:username 字段设置了 unique=True,重复插入相同用户名;
    • 解决:更换用户名,或先查询是否已存在该用户。
  3. 查询不到数据
    • 原因:未执行 session.commit(),数据仅存于会话、未写入数据库;
    • 解决:所有新增 / 修改操作后必须调用 session.commit()
  4. 多对多关联表报错
    • 原因:原代码中误将 Table 写成 Column(已修正);
    • 解决:关联表需用 sqlalchemy.Table 定义,而非 Column
  5. 一对一关系数据重复
    • 原因:未给 user_id 设置 unique=True
    • 解决:确保 user_id = Column(..., unique=True),限制一个用户仅对应一份资料。

七、总结

本教程完成了以下核心内容:

  1. 理解 ORM 核心概念,掌握 SQLAlchemy 基础配置;
  2. 定义 4 张核心表(User/Post/Tag/UserProfile),覆盖一对多、多对多、一对一 3 种核心关系;
  3. 实现完整的增删改查操作,结合 ER 图直观理解表关系;
  4. 解决高频报错问题,确保代码可直接运行。

八、引用

  1. SQLAlchemy 官方文档
  2. SQLite 官方主页
相关推荐
好家伙VCC4 小时前
# 发散创新:基于事件驱动架构的实时日志监控系统设计与实现在现代分布式系统中,**事件驱动编程模型**正
java·python·架构
测试19984 小时前
postman接口测试详解
自动化测试·软件测试·python·测试工具·测试用例·接口测试·postman
SuniaWang4 小时前
Java 17实战:Record与密封类的黄金搭档
java·开发语言·python
时光不写代码4 小时前
修复 pytest-asyncio 事件循环冲突:完整解决方案
python·pytest·fastapi
2401_827499994 小时前
python项目实战10-网络机器人03
开发语言·python·php
小江的记录本4 小时前
【Transformer架构】Transformer架构核心知识体系(包括自注意力机制、多头注意力、Encoder-Decoder结构)
java·人工智能·后端·python·深度学习·架构·transformer
7年前端辞职转AI4 小时前
Python 注释
python·编程语言
xcjbqd04 小时前
CSS如何给Bootstrap侧边菜单加图标_使用font-awesome结合CSS
jvm·数据库·python
坐吃山猪5 小时前
Python09_正则表达式
开发语言·python·正则表达式