SQLAlchemy 入门教程:使用 Python 连接数据库
在现代软件开发中,与数据库交互是不可或缺的一部分。无论是 Web 应用、桌面程序还是数据分析脚本,数据持久化都扮演着核心角色。虽然我们可以直接使用数据库驱动编写原生 SQL 语句与数据库通信,但这通常会导致代码冗余、难以维护,并且在切换数据库时会带来巨大的麻烦。
SQLAlchemy 是一个功能强大且灵活的 Python SQL 工具包和对象关系映射器 (ORM)。它弥补了 Python 对象和数据库表之间的差异,允许开发者使用 Python 类和对象来操作数据库,而无需编写大量的重复性 SQL。SQLAlchemy 提供了一套连贯的、Pythonic 的接口,支持多种数据库后端,并且在性能和灵活性之间取得了很好的平衡。
本篇文章将带你深入了解 SQLAlchemy 的基础知识,重点讲解如何使用 Python 连接数据库,并通过 SQLAlchemy 的核心(SQLAlchemy Core)和 ORM(SQLAlchemy ORM)进行基本的数据库操作。
为什么选择 SQLAlchemy?
在使用 SQLAlchemy 之前,你可能会问:“为什么不直接使用 sqlite3
、psycopg2
或 mysql-connector-python
这些数据库驱动呢?” 直接使用驱动当然可以,但 SQLAlchemy 提供了以下显著优势:
- 数据库抽象层: SQLAlchemy 提供了一个通用的接口,屏蔽了底层数据库实现的差异。这意味着你可以编写一次代码,然后轻松切换到不同的数据库(如 PostgreSQL, MySQL, SQLite, Oracle 等),而无需重写大部分数据库交互逻辑。
- Pythonic 的接口: SQLAlchemy 允许你使用 Python 对象和方法来表示数据库表、列和查询,这使得代码更加直观、易读、易写。
- ORM (Object-Relational Mapper): SQLAlchemy 的 ORM 将数据库表映射到 Python 类,将数据库行映射到 Python 对象。这使得你可以像操作普通 Python 对象一样操作数据库记录,极大地提高了开发效率。
- SQL 表达式语言 (Core): 对于需要更精细控制或进行复杂查询的场景,SQLAlchemy Core 提供了一个 SQL 表达式语言,允许你以编程方式构建 SQL 语句,这比手动拼接字符串安全得多,并且更具可读性。
- 连接池和事务管理: SQLAlchemy 提供了成熟的连接池管理和事务管理机制,帮助你编写出高性能、可靠的数据库应用。
- 安全性: 使用 SQLAlchemy(尤其是 ORM)可以有效防止 SQL 注入等常见的安全漏洞,因为它会自动处理参数绑定。
总而言之,SQLAlchemy 是 Python 生态系统中功能最全面、最稳定、最流行的数据库工具之一,无论对于小型项目还是大型企业级应用,都是一个极好的选择。
准备工作
在开始之前,请确保你已经安装了 Python 环境。本教程使用的 Python 版本为 3.6 或更高。
你需要安装 SQLAlchemy 本身以及你计划连接的数据库的 Python 驱动。
-
安装 SQLAlchemy:
bash
pip install SQLAlchemy -
安装数据库驱动:
- SQLite: Python 标准库内置了
sqlite3
模块,通常不需要额外安装。 - PostgreSQL:
bash
pip install psycopg2-binary - MySQL:
bash
pip install mysql-connector-python
# 或使用 PyMySQL:
# pip install PyMySQL - 其他数据库: 请查阅 SQLAlchemy 官方文档或对应数据库驱动的文档。
- SQLite: Python 标准库内置了
为了简单起见,本教程将以 SQLite 数据库为例进行讲解。SQLite 是一个轻量级的、基于文件的数据库,无需独立的服务器进程,非常适合入门学习和小型项目。
连接数据库:使用 create_engine
与数据库建立连接是使用 SQLAlchemy 的第一步。SQLAlchemy 使用 create_engine
函数来创建一个 Engine
对象。Engine
是一个数据库连接池和方言(Dialect)的组合,它负责管理与数据库的底层通信。
create_engine
接收一个数据库连接 URL 作为参数。这个 URL 遵循特定的格式:
dialect+driver://user:password@host:port/database
各部分的含义:
dialect
: 数据库类型,例如sqlite
,postgresql
,mysql
,oracle
等。driver
: 可选,用于连接数据库的特定驱动库的名称,例如psycopg2
(PostgreSQL),mysqlconnector
(MySQL),pymysql
(MySQL)。如果省略,SQLAlchemy 会尝试使用默认驱动。user
: 数据库用户名。password
: 数据库密码。host
: 数据库服务器地址。port
: 可选,数据库服务器端口。database
: 数据库名称(对于 SQLite 是数据库文件的路径)。
SQLite 连接示例:
对于 SQLite,连接 URL 非常简单,只需要指定 sqlite://
后跟数据库文件的路径。
- 连接到当前目录下的
mydatabase.db
文件:
python
# mydatabase.db 将在当前目录创建
database_url = "sqlite:///mydatabase.db" - 连接到内存中的 SQLite 数据库(数据不会被保存):
python
# 数据只存在于内存中,程序结束后丢失
database_url = "sqlite:///:memory:"
让我们编写代码来创建 Engine
并建立连接:
“`python
from sqlalchemy import create_engine
使用 SQLite 数据库,文件名为 mydatabase.db,它将在当前目录创建
database_url = “sqlite:///mydatabase.db”
创建一个 Engine 对象
engine = create_engine(database_url)
现在 engine 对象已经创建,但并没有立即连接到数据库
实际的连接会在你需要执行操作时才建立(惰性连接)
print(f”成功创建数据库引擎,连接到: {database_url}”)
注意:在实际应用中,你通常只需要创建一次 engine 对象,并在整个应用生命周期中复用。
SQLAlchemy 会自动管理连接池。
“`
运行这段代码,你不会看到数据库文件被创建,因为 create_engine
默认是惰性连接。连接是在你第一次尝试与数据库交互时才建立的。
执行原始 SQL:使用 Engine 和 Connection
虽然 SQLAlchemy 的 ORM 更常用,但了解如何使用 Engine
和 Connection
对象来执行原始 SQL 语句也非常重要。这在某些特定场景下(例如执行数据库管理任务、处理非常复杂的自定义查询、或者仅仅是为了理解底层机制)会很有用。
Engine
对象本身不是连接,它是一个连接工厂和配置容器。要执行 SQL,你需要从 Engine
获取一个 Connection
对象。推荐使用 with
语句来管理 Connection
,确保连接在操作完成后被正确关闭。
“`python
from sqlalchemy import create_engine, text # 导入 text 函数来包裹原始 SQL
database_url = “sqlite:///mydatabase.db”
engine = create_engine(database_url)
使用 ‘with’ 语句获取并管理连接
with engine.connect() as connection:
# 执行一个简单的 SQL 语句,例如创建表
try:
# 使用 text() 函数包裹原始 SQL 字符串,提高兼容性和可读性
create_table_sql = text(“””
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name VARCHAR(50) NOT NULL,
age INTEGER
)
“””)
connection.execute(create_table_sql)
print(“表 ‘users’ 已创建或已存在。”)
# 插入一些数据
insert_data_sql = text("""
INSERT INTO users (name, age) VALUES ('Alice', 30)
""")
connection.execute(insert_data_sql)
connection.execute(text("INSERT INTO users (name, age) VALUES (:name, :age)").bindparams(name='Bob', age=25)) # 使用参数绑定更安全
print("数据已插入。")
# 提交事务 (对于 INSERT, UPDATE, DELETE 操作需要提交)
connection.commit()
print("事务已提交。")
# 查询数据
select_data_sql = text("SELECT id, name, age FROM users")
result = connection.execute(select_data_sql)
print("\n查询结果:")
# result 是一个 ResultProxy 对象,可以像迭代器一样遍历
for row in result:
# row 是一个 RowProxy 对象,可以通过索引或列名访问数据
print(f"ID: {row[0]}, Name: {row.name}, Age: {row['age']}")
except Exception as e:
print(f"发生错误: {e}")
# 如果发生错误,回滚事务
connection.rollback()
print("事务已回滚。")
‘with’ 块结束时,连接会自动关闭并返回到连接池
print(“\n连接已关闭。”)
“`
解释:
text()
: 这是一个重要的函数,用于包裹你想要执行的原始 SQL 字符串。虽然对于一些简单的语句不是强制的,但使用text()
可以让 SQLAlchemy 更好地理解你的意图,提高跨数据库兼容性,并且允许使用参数绑定 (.bindparams()
),这是一种防止 SQL 注入的安全做法。connection.execute()
: 执行 SQL 语句。对于 SELECT 语句,它会返回一个ResultProxy
对象;对于 INSERT/UPDATE/DELETE 等语句,它通常返回None
或一个表示影响行数的对象。connection.commit()
: 提交当前事务。对于修改数据的操作(INSERT, UPDATE, DELETE),你需要调用commit()
才能将更改永久保存到数据库。connection.rollback()
: 回滚当前事务。如果在事务过程中发生错误,可以调用rollback()
来取消所有未提交的更改。ResultProxy
: 查询结果的迭代器。RowProxy
: 结果中的每一行,可以像元组或字典一样访问数据。
通过 Engine
和 Connection
执行原始 SQL 简单直接,但你需要自己管理 SQL 语句、表结构、列名等,这正是 ORM 试图解决的问题。
进入 ORM 世界:映射 Python 对象到数据库表
SQLAlchemy 的 ORM 是其最强大和最常用的功能之一。它允许你定义 Python 类,并将这些类映射到数据库表。每个类的实例代表表中的一行,类的属性代表表中的列。通过 ORM,你可以像操作 Python 对象一样方便地进行数据库的增、删、改、查。
ORM 的核心概念包括:
- 映射 (Mapping): 将 Python 类映射到数据库表。
- 声明性映射 (Declarative Mapping): 一种更现代、更易用的映射方式,允许你在定义类时直接声明它对应的表和列。
- 基类 (Base): 声明性映射的起点,通常通过
declarative_base()
创建。 - Session: 与数据库交互的主要接口,它管理着 Python 对象的生命周期和与数据库之间的同步。
定义数据库结构:使用声明性映射 (Declarative Base)
使用声明性映射,你只需要定义 Python 类,并继承一个基类,然后指定类对应的表名以及类的属性对应的列。
“`python
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import declarative_base
1. 创建数据库引擎
database_url = “sqlite:///mydatabase.db”
engine = create_engine(database_url)
2. 创建声明性映射的基类
Base = declarative_base()
3. 定义映射类(对应数据库表)
class User(Base):
# tablename 指定这个类映射到哪张表
tablename = ‘users’
# 定义属性(对应表中的列)
# Column 是一个描述数据库列的对象
# Integer, String 等是 SQLAlchemy 提供的数据类型
id = Column(Integer, primary_key=True) # primary_key=True 表示这是主键
name = Column(String(50), nullable=False) # String(长度),nullable=False 表示不允许为空
age = Column(Integer)
# 可选:添加一个 __repr__ 方法,方便打印对象信息
def __repr__(self):
return f"<User(id={self.id}, name='{self.name}', age={self.age})>"
现在,我们已经定义了一个 User 类,它映射到 ‘users’ 表,并且有 id, name, age 三列。
4. 创建表结构到数据库中
Base.metadata 包含了所有通过 Base 定义的表的信息
create_all 会检查数据库中是否存在这些表,如果不存在则创建
Base.metadata.create_all(engine)
print(“通过 ORM 定义并创建了 ‘users’ 表。”)
“`
解释:
declarative_base()
: 创建一个Base
类。所有需要映射到数据库的类都应该继承自这个Base
类。__tablename__
: 类属性,指定了该类对应的数据库表名。Column()
: 用于定义类属性与数据库列的映射关系。第一个参数是列的数据类型 (如Integer
,String
,Boolean
,Float
,Date
,DateTime
等)。后续参数用于指定列的约束,如primary_key=True
,nullable=False
,unique=True
,default=...
等。Base.metadata.create_all(engine)
: 这个方法遍历Base
中定义的所有继承类,并根据它们的定义在指定的engine
连接的数据库中创建相应的表。如果表已经存在,它默认不会做任何事情。
通过这种方式,我们使用 Python 的面向对象方式定义了数据库结构,代码更加清晰、易于理解和维护。
使用 Session 进行 ORM 操作
定义了映射类和创建了表之后,我们需要一个接口来实际地进行数据的增、删、改、查。在 SQLAlchemy ORM 中,这个接口就是 Session。
Session
是 ORM 的核心工作区域。它扮演着以下角色:
- 事务管理器:
Session
管理着一个或多个数据库操作组成的事务。你需要通过session.commit()
提交事务,或者session.rollback()
回滚事务。 - 对象管理器:
Session
跟踪你添加到其中的 Python 对象(例如User
类的实例),并管理它们的状态(是新的、已修改的、已删除的等等)。 - 查询接口:
Session
提供了session.query()
方法来执行数据库查询,并以 Python 对象的形式返回结果。
创建 Session:
你需要通过 sessionmaker
函数创建一个 Session 工厂,然后通过这个工厂创建 Session 实例。
“`python
from sqlalchemy.orm import sessionmaker
… (前面的 create_engine, declarative_base, User 类定义和 Base.metadata.create_all(engine) 代码) …
1. 创建 Session 工厂
bind 参数将 Session 与之前创建的 Engine 绑定
Session = sessionmaker(bind=engine)
2. 创建 Session 实例
推荐使用 ‘with’ 语句管理 Session,确保它在使用后被关闭
session = Session() # 另一种方式,但需要手动 session.close()
“`
使用 Session 进行 CRUD 操作 (增、删、改、查)
现在我们有了 Session
,可以开始进行实际的数据库操作了。
C (Create – 增加数据)
要添加新的数据行,你只需创建映射类的实例,然后将其添加到 Session 中,最后提交 Session。
“`python
… (前面的导入、engine、Base、User 类和 Session 工厂定义) …
创建 Session 实例
with Session() as session:
# 创建 User 对象
new_user1 = User(name=’Charlie’, age=35)
new_user2 = User(name=’David’, age=28)
# 将对象添加到 Session 中
session.add(new_user1)
session.add(new_user2)
# 或者一次添加多个对象
# session.add_all([new_user1, new_user2])
# 提交 Session,将更改保存到数据库
session.commit()
print("新增用户: Charlie, David")
# 提交后,对象的 id 属性通常会被数据库填充(如果使用了自增主键)
print(f"Charlie 的 ID: {new_user1.id}")
print(f"David 的 ID: {new_user2.id}")
with 块结束时,session 会自动关闭
“`
解释:
session.add(instance)
: 将一个新的对象标记为待插入到数据库。session.add_all([instance1, instance2, ...])
: 批量添加多个对象。session.commit()
: 将 Session 中所有待处理的更改(新增、修改、删除)发送到数据库并提交事务。
R (Read – 查询数据)
查询是 ORM 最常用的功能之一。使用 session.query(YourClass)
方法来构建查询,然后使用各种方法(如 all()
, first()
, filter()
, filter_by()
, order_by()
, limit()
等)来细化和执行查询。
“`python
… (前面的导入、engine、Base、User 类和 Session 工厂定义) …
with Session() as session:
print(“\n查询所有用户:”)
# 查询 User 类对应的所有数据
all_users = session.query(User).all()
for user in all_users:
print(user) # 使用了 User 类中定义的 repr 方法
print("\n查询第一个用户:")
# 查询第一条记录
first_user = session.query(User).first()
if first_user:
print(first_user)
print("\n按条件查询(age > 30):")
# 使用 filter() 进行条件过滤
# 注意条件使用类属性 User.age
users_over_30 = session.query(User).filter(User.age > 30).all()
for user in users_over_30:
print(user)
print("\n按条件查询(name = 'Bob'):")
# 使用 filter_by() 进行等值过滤(更简洁)
bob = session.query(User).filter_by(name='Bob').first()
if bob:
print(bob)
print("\n按条件查询(name like '%a%'):")
# 使用 like 进行模糊查询
users_with_a = session.query(User).filter(User.name.like('%a%')).all()
for user in users_with_a:
print(user)
print("\n按年龄降序查询:")
# 使用 order_by() 排序,desc() 表示降序
users_ordered_desc = session.query(User).order_by(User.age.desc()).all()
for user in users_ordered_desc:
print(user)
print("\n限制查询数量:")
# 使用 limit() 限制结果数量
limited_users = session.query(User).limit(2).all()
for user in limited_users:
print(user)
“`
解释:
session.query(User)
: 开始一个针对User
类的查询。.all()
: 执行查询并返回所有匹配的结果,结果是一个列表,包含User
对象。.first()
: 执行查询并返回第一个匹配的结果,结果是单个User
对象或None
。.filter(condition)
: 添加一个过滤条件。condition
通常使用类的属性和比较运算符(==
,!=
,>
,<
,>=
,<=
,is
,isnot
等)构建。.filter_by(column=value)
: 添加一个等值过滤条件,更简洁。.like()
: 用于模糊匹配,通常与字符串属性一起使用。.order_by(column)
: 指定结果的排序方式。.asc()
升序 (默认),.desc()
降序。.limit(n)
: 限制返回的结果数量。- 查询方法可以链式调用,例如
session.query(User).filter(User.age > 20).order_by(User.name).limit(5).all()
。
U (Update – 修改数据)
要修改数据,首先通过查询获取到对应的对象,然后修改对象的属性,最后提交 Session。
“`python
… (前面的导入、engine、Base、User 类和 Session 工厂定义) …
with Session() as session:
print(“\n更新用户 Bob 的年龄:”)
# 查询到要更新的对象
bob = session.query(User).filter_by(name=’Bob’).first()
if bob:
# 修改对象的属性
bob.age = 26
print(f"Bob 的年龄更新为: {bob.age}")
# 提交 Session
session.commit()
print("更新已提交。")
else:
print("未找到用户 Bob。")
验证更新是否成功
with Session() as session:
updated_bob = session.query(User).filter_by(name=’Bob’).first()
if updated_bob:
print(f”验证更新后的 Bob: {updated_bob}”)
“`
解释:
- 当你从 Session 中查询到一个对象时,它就被 Session 跟踪了。
- 修改对象的属性会自动被 Session 检测到。
- 调用
session.commit()
时,Session 会生成相应的 UPDATE SQL 语句发送到数据库。
D (Delete – 删除数据)
要删除数据,首先通过查询获取到对应的对象,然后使用 session.delete()
方法将其标记为待删除,最后提交 Session。
“`python
… (前面的导入、engine、Base、User 类和 Session 工厂定义) …
with Session() as session:
print(“\n删除用户 Charlie:”)
# 查询到要删除的对象
charlie = session.query(User).filter_by(name=’Charlie’).first()
if charlie:
# 将对象标记为待删除
session.delete(charlie)
print(f"用户 {charlie.name} 标记为待删除。")
# 提交 Session
session.commit()
print("删除已提交。")
else:
print("未找到用户 Charlie。")
验证删除是否成功
with Session() as session:
deleted_charlie = session.query(User).filter_by(name=’Charlie’).first()
if deleted_charlie is None:
print(“用户 Charlie 已成功删除。”)
else:
print(“用户 Charlie 未被删除。”)
“`
解释:
session.delete(instance)
: 将一个对象标记为待从数据库中删除。- 调用
session.commit()
时,Session 会生成相应的 DELETE SQL 语句发送到数据库。
Session 的生命周期与异常处理
正确管理 Session
的生命周期至关重要。如前所述,推荐使用 with Session() as session:
语句。它确保了 Session 在代码块结束时被正确关闭,即使发生异常也是如此。
在进行数据库操作时,可能会发生各种错误(如违反唯一约束、连接中断等)。在 ORM 操作中,如果发生错误,数据库事务可能会处于不确定状态。因此,在生产代码中,你应该总是结合 try...except
和 session.rollback()
来处理潜在的错误。
“`python
… (前面的导入、engine、Base、User 类和 Session 工厂定义) …
尝试添加一个可能引发错误的用户(例如,如果 name 字段有唯一约束)
try:
with Session() as session:
print(“\n尝试添加一个用户:”)
# 假设我们有一个 name 唯一的约束,再次添加 Bob
# 注意:我们这个简单的例子中 name 没有唯一约束,这里只是演示rollback
# 如果 name 有唯一约束,下面的代码会抛异常
duplicate_user = User(name=’Bob’, age=99) # Bob 已经存在
session.add(duplicate_user)
session.commit() # 尝试提交
print("用户添加成功(如果没报错的话)")
except Exception as e:
# 如果发生任何异常
print(f”\n添加用户时发生错误: {e}”)
# 确保回滚事务,清除 Session 中的待处理更改
# 注意:当异常发生在 with 块内部时,with 语句会自动调用 rollback() 再 raise 异常
# 但 explicit rollback() is good for clarity or specific complex scenarios
# session.rollback() # with 语句会自动处理
print(“事务已回滚。”)
再次查询 Bob,确认数据没有被错误添加
with Session() as session:
all_bobs = session.query(User).filter_by(name=’Bob’).all()
print(f”\n查询所有名字为 Bob 的用户(确认回滚结果):”)
for bob_user in all_bobs:
print(bob_user)
“`
在 with Session() as session:
结构中,如果 with
块内的代码成功执行完毕,session.commit()
会被自动调用;如果发生任何异常,session.rollback()
会被自动调用,然后异常会被重新抛出。这是推荐的 Session 管理模式。
SQLAlchemy Core vs. ORM 小结
到目前为止,我们接触了 SQLAlchemy 的两个主要组件:
- SQLAlchemy Core: 提供了
create_engine
,Connection
,text()
,Table
,Column
,MetaData
等,用于构建和执行 SQL 表达式。它更接近底层的 SQL 操作,提供了最大的灵活性和对 SQL 的精确控制。适合执行复杂的、数据库特定的查询,或者进行模式操作(创建/修改表等)。 - SQLAlchemy ORM: 构建在 Core 之上,提供了
declarative_base
,Session
,query()
等,用于将 Python 类映射到数据库表,并以面向对象的方式进行数据操作。它提供了更高的抽象级别,简化了常见的 CRUD 操作,提高了开发效率,并使得代码更加 Pythonic。
对于大多数应用开发来说,ORM 是首选,因为它大大简化了与数据库的交互。然而,当你需要执行 ORM 难以表达的复杂查询、或者出于性能考虑需要编写更优化的 SQL 时,可以结合使用 ORM 和 Core,甚至直接在 ORM 的 Session 中执行 Core 表达式或原始 SQL。
进一步探索
本教程仅仅是 SQLAlchemy 入门的起点。SQLAlchemy 还有许多强大的功能等待你去探索:
- 关系 (Relationships): 如何定义一对多、多对多等表之间的关联,并使用 ORM 进行关联查询。
- Join 操作: 如何在 ORM 中执行 JOIN 查询。
- 索引和约束: 如何在 ORM 模型中定义索引和更复杂的约束。
- 数据类型: SQLAlchemy 支持丰富的数据类型,以及自定义类型。
- 迁移工具: alembic 是 SQLAlchemy 官方推荐的数据库迁移工具,用于管理数据库模式的演变。
- 连接池配置: 针对生产环境,你需要根据负载调整连接池的配置。
- 性能优化: 了解如何通过各种技术(如惰性加载、预加载、使用 Core 表达式等)来优化查询性能。
- 使用其他数据库: 连接到 PostgreSQL, MySQL 等数据库的细节。
学习这些更高级的主题将帮助你构建更健壮、更复杂、性能更优的数据库应用。
总结
通过本文的学习,你应该已经掌握了使用 SQLAlchemy 连接数据库的基础知识:
- 了解了 SQLAlchemy 是什么以及它的优势。
- 学会了如何使用
create_engine
创建数据库引擎。 - 了解了如何使用
Engine
和Connection
执行原始 SQL 语句。 - 掌握了使用声明性映射 (
declarative_base
,Column
,__tablename__
) 定义数据库表对应的 Python 类。 - 学会了使用
Base.metadata.create_all()
创建数据库表。 - 理解了
Session
在 ORM 中的作用。 - 学会了使用
Session
进行基本的 CRUD 操作(增加、查询、修改、删除数据)。 - 了解了 Session 的生命周期管理和异常处理的重要性。
SQLAlchemy 是一个值得投入时间学习的强大工具。它能够让你更高效、更优雅地处理数据库交互。从这里开始,你可以查阅 SQLAlchemy 官方文档,它是最权威的学习资源,详细介绍了所有特性和更高级的用法。
希望这篇详细的入门教程对你有所帮助!祝你在 SQLAlchemy 的学习之旅中一切顺利!