在 Python 開發中,操作 MySQL 資料庫主要有兩種主流方式:使用原生驅動 pymysql 直接執行 SQL,或是透過 SQLAlchemy ORM 進行物件關聯映射操作。本文將系統性介紹這兩種方式的核心用法、進階技巧與最佳實踐,幫助你快速上手並避開常見坑點。
pymysql 是純 Python 實現的 MySQL 客戶端庫,API 設計貼近 DB-API 2.0 標準,適合需要精細控制 SQL 或效能敏感的場景。
pip3 install pymysql
建立連線時建議使用字典游標(DictCursor),回傳結果為字典格式,欄位名可直接作為鍵存取,程式碼更易讀:
import pymysql
conn = pymysql.connect(
host='127.0.0.1', port=3306,
user='root', passwd='123', db='t1',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
所有操作皆透過游標進行。修改資料後必須 conn.commit() 提交事務,否則不會持久化。
cursor = conn.cursor()
# 參數化查詢,防止 SQL 注入
effect_row = cursor.execute(
"UPDATE hosts SET host = %s WHERE nid > %s",
('1.1.1.2', 1)
)
# 批次新增
cursor.executemany(
"INSERT INTO hosts(host, color_id) VALUES (%s, %s)",
[("1.1.1.11", 1), ("1.1.1.12", 2)]
)
conn.commit()
cursor.close()
conn.close()
插入資料後,cursor.lastrowid 可取得最新自增主鍵值。查詢結果提供三種擷取方式:
fetchone():單筆fetchmany(n):前 n 筆fetchall():全部使用 cursor.scroll(offset, mode='relative|absolute') 移動游標位置。若需字典型結果,建立連線時指定 cursorclass=pymysql.cursors.DictCursor 即可。
contextlib 簡化資源管理利用上下文管理器自動處理連線開關與事務提交/回滾:
import contextlib
@contextlib.contextmanager
def mysql_conn(**cfg):
conn = pymysql.connect(**cfg, cursorclass=pymysql.cursors.DictCursor)
cursor = conn.cursor()
try:
yield cursor
conn.commit()
except Exception:
conn.rollback()
raise
finally:
cursor.close()
conn.close()
# 使用範例
with mysql_conn(host='127.0.0.1', user='root', passwd='123', db='t1') as cur:
cur.execute("SELECT * FROM hosts")
print(cur.fetchall())
SQLAlchemy 是 Python 生態系中最強大的 ORM 框架,核心理念是「將類別映射為資料表,將物件實例映射為資料列」。它建立在 DBAPI(如 pymysql)之上,支援多種資料庫方言。
pip3 install SQLAlchemy pymysql
連線字串格式:mysql+pymysql://user:pass@host:port/dbname?charset=utf8mb4。create_engine 內建連線池,max_overflow 控制額外連線數。
from sqlalchemy import create_engine
engine = create_engine(
"mysql+pymysql://root:123@127.0.0.1:3306/t1?charset=utf8mb4",
max_overflow=5, pool_pre_ping=True
)
若不想定義 ORM 模型,可直接用 engine.execute() 執行原生 SQL,回傳 ResultProxy 支援 fetchone()/fetchall() 等方法。
繼承 declarative_base() 產生的基類,定義類別屬性對應欄位:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Index
from sqlalchemy.orm import relationship, sessionmaker
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(32), nullable=False)
extra = Column(String(16))
__table_args__ = (
UniqueConstraint('id', 'name', name='uix_id_name'),
Index('ix_name_extra', 'name', 'extra'),
)
def __repr__(self):
return f""
# 一對多關聯
class Favor(Base):
__tablename__ = 'favor'
nid = Column(Integer, primary_key=True)
caption = Column(String(50), default='red', unique=True)
class Person(Base):
__tablename__ = 'person'
nid = Column(Integer, primary_key=True)
name = Column(String(32), index=True)
favor_id = Column(Integer, ForeignKey('favor.nid'))
favor = relationship("Favor", backref='persons')
# 多對多關聯(需中間表)
class ServerToGroup(Base):
__tablename__ = 'server_to_group'
nid = Column(Integer, primary_key=True, autoincrement=True)
server_id = Column(Integer, ForeignKey('server.id'))
group_id = Column(Integer, ForeignKey('group.id'))
server = relationship("Server", backref='s2g')
group = relationship("Group", backref='s2g')
class Group(Base):
__tablename__ = 'group'
id = Column(Integer, primary_key=True)
name = Column(String(64), unique=True, nullable=False)
port = Column(Integer, default=22)
class Server(Base):
__tablename__ = 'server'
id = Column(Integer, primary_key=True, autoincrement=True)
hostname = Column(String(64), unique=True, nullable=False)
groups = relationship("Group", secondary="server_to_group", backref='servers')
def init_db():
Base.metadata.create_all(engine)
def drop_db():
Base.metadata.drop_all(engine)
sessionmaker(bind=engine) 建立 Session 工廠,每次操作實例化一個 Session 物件:
Session = sessionmaker(bind=engine)
session = Session()
# 新增
user = User(name="張三", extra="三兒")
session.add(user)
session.add_all([User(name="李四", extra="四兒"), User(name="王五", extra="五兒")])
session.commit()
# 刪除
session.query(User).filter(User.id > 2).delete()
session.commit()
# 修改
session.query(User).filter(User.id > 2).update({"name": "nick", "extra": "updated"})
session.commit()
# 查詢
users = session.query(User).all()
user = session.query(User).filter_by(name='nick').first()
names = session.query(User.name, User.extra).all()
from sqlalchemy import and_, or_, func
from sqlalchemy.sql import func
# 條件組合
session.query(User).filter(and_(User.id > 3, User.name == 'nick')).all()
session.query(User).filter(or_(User.id < 2, User.name == 'nick')).all()
# 模糊查詢
session.query(User).filter(User.name.like('n%')).all()
# 排序、分頁
session.query(User).order_by(User.name.desc(), User.id.asc()).limit(10).offset(20).all()
# 聚合與分組
session.query(func.max(User.id), func.count(User.id)).group_by(User.name).having(func.min(User.id) > 2).all()
# 連表查詢
session.query(User, Favor).filter(User.id == Favor.nid).all()
session.query(Person).join(Favor).all() # INNER JOIN
session.query(Person).join(Favor, isouter=True).all() # LEFT JOIN
# 集合運算
q1 = session.query(User.name).filter(User.id > 2)
q2 = session.query(Favor.caption).filter(Favor.nid < 2)
q1.union(q2).all() # UNION 去重
q1.union_all(q2).all() # UNION ALL 保留重複
總結:pymysql 適合輕量、高效能、需手寫 SQL 的場景;SQLAlchemy ORM 則在複雜業務邏輯、關聯查詢、遷移維護上優勢明顯。實際專案常混合使用:核心高頻查詢用 Core/原生 SQL,業務邏輯用 ORM,取長補短。