在 Python 開發中,操作 MySQL 資料庫主要有兩種主流方式:使用原生驅動模組 PyMySQL 進行底層 SQL 操作,或採用 SQLAlchemy ORM 框架進行物件關係映射開發。本文將系統性介紹這兩種方案的安裝、配置與核心用法,並提供完整程式碼範例供參考。
PyMySQL 是純 Python 實現的 MySQL 客戶端庫,API 設計與 MySQLdb 高度相容,適合需要精細控制 SQL 執行的場景。
pip3 install pymysql
建立連線、創建游標、執行 SQL 並提交事務的標準流程如下:
import pymysql
# 建立資料庫連線
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='root',
passwd='123',
db='t1',
charset='utf8mb4'
)
# 創建游標
cursor = conn.cursor()
# 執行單條更新語句
effect_row = cursor.execute("UPDATE hosts SET host = '1.1.1.2'")
# 參數化查詢防止 SQL 注入
# effect_row = cursor.execute("UPDATE hosts SET host = %s WHERE nid > %s", ('1.1.1.2', 1))
# 批量插入
# effect_row = cursor.executemany(
# "INSERT INTO hosts(host, color_id) VALUES(%s, %s)",
# [("1.1.1.11", 1), ("1.1.1.11", 2)]
# )
# 提交事務(必須執行,否則修改不生效)
conn.commit()
# 關閉資源
cursor.close()
conn.close()
執行插入操作後,可透過 cursor.lastrowid 獲取最新生成的自增 ID:
cursor.executemany(
"INSERT INTO hosts(host, color_id) VALUES(%s, %s)",
[("1.1.1.11", 1), ("1.1.1.11", 2)]
)
conn.commit()
new_id = cursor.lastrowid # 獲取最後一條插入記錄的自增 ID
print(f"新記錄 ID: {new_id}")
PyMySQL 提供三種游標獲取數據的方法:
fetchone():獲取單條記錄fetchmany(size):獲取指定數量記錄fetchall():獲取所有記錄cursor.execute("SELECT * FROM hosts")
row_1 = cursor.fetchone() # 第一行
# row_2 = cursor.fetchmany(3) # 前三行
# row_3 = cursor.fetchall() # 所有行
使用 cursor.scroll(offset, mode) 移動游標位置:
cursor.scroll(1, mode='relative'):相對當前位置移動cursor.scroll(2, mode='absolute'):相對絕對位置移動預設游標返回元組,指定 DictCursor 可返回字典格式數據:
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
cursor.execute("SELECT * FROM hosts")
row = cursor.fetchone()
# row 格式:{'id': 1, 'host': '1.1.1.1', 'color_id': 1}
SQLAlchemy 是 Python 生態中最成熟的 ORM 框架,支援 Core(SQL 表達式語言)與 ORM 兩種操作模式,底層依賴 Dialect 機制適配不同資料庫驅動。
pip3 install SQLAlchemy
常見連線字串格式:
# PyMySQL 驅動 mysql+pymysql://username:password@host/dbname[?options] # MySQLdb 驅動 mysql+mysqldb://user:password@host:port/dbname # MySQL Connector mysql+mysqlconnector://user:password@host:port/dbname
使用 create_engine 建立引擎,直接執行 SQL:
from sqlalchemy import create_engine
engine = create_engine(
"mysql+pymysql://root:123@127.0.0.1:3306/t1",
max_overflow=5, # 連線池溢出數量
echo=True # 開啟 SQL 日誌(開發環境建議開啟)
)
# 執行插入並獲取自增 ID
# result = engine.execute("INSERT INTO hosts (host, color_id) VALUES ('1.1.1.22', 3)")
# print(result.lastrowid)
# 批量插入(參數化)
# engine.execute(
# "INSERT INTO hosts (host, color_id) VALUES(%s, %s)",
# [('1.1.1.22', 3), ('1.1.1.221', 3)]
# )
# 命名參數風格
# engine.execute(
# "INSERT INTO hosts (host, color_id) VALUES (%(host)s, %(color_id)s)",
# host='1.1.1.99', color_id=3
# )
# 查詢操作
# result = engine.execute('SELECT * FROM hosts')
# result.fetchone() # 單條
# result.fetchmany(3) # 多條
# result.fetchall() # 全部
定義模型類,自動建表並進行物件操作:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Index
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy import create_engine
engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/t1", max_overflow=5)
Base = declarative_base()
# 單表模型
class Users(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(32))
extra = Column(String(16))
__table_args__ = (
UniqueConstraint('id', 'name', name='uix_id_name'),
Index('ix_id_name', '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, nullable=True)
favor_id = Column(Integer, ForeignKey("favor.nid"))
favor = relationship("Favor", backref='persons') # 關聯屬性
# 多對多關聯
class ServerToGroup(Base):
__tablename__ = 'servertogroup'
nid = Column(Integer, primary_key=True, autoincrement=True)
server_id = Column(Integer, ForeignKey('server.id'))
group_id = Column(Integer, ForeignKey('group.id'))
group = relationship("Group", backref='server_links')
server = relationship("Server", backref='group_links')
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)
# 建表 / 刪表
def init_db():
Base.metadata.create_all(engine)
def drop_db():
Base.metadata.drop_all(engine)
透過 sessionmaker 創建會話,進行 CRUD 操作:
Session = sessionmaker(bind=engine)
session = Session()
# === 增 ===
obj = Users(name="alex0", extra='sb')
session.add(obj)
session.add_all([
Users(name="alex1", extra='sb'),
Users(name="alex2", extra='sb'),
])
session.commit()
# === 刪 ===
session.query(Users).filter(Users.id > 2).delete()
session.commit()
# === 改 ===
session.query(Users).filter(Users.id > 2).update({"name": "099"})
# 使用列運算式更新
session.query(Users).filter(Users.id > 2).update(
{Users.name: Users.name + "099"},
synchronize_session=False
)
session.commit()
# === 查 ===
users = session.query(Users).all()
names = session.query(Users.name, Users.extra).all()
alex = session.query(Users).filter_by(name='alex').first()
# === 進階查詢 ===
from sqlalchemy import and_, or_, func
# 條件組合
session.query(Users).filter(and_(Users.id > 3, Users.name == 'eric')).all()
session.query(Users).filter(or_(Users.id < 2, Users.name == 'eric')).all()
# 通配符
session.query(Users).filter(Users.name.like('e%')).all()
# 分頁與排序
session.query(Users)[1:2] # LIMIT 1 OFFSET 1
session.query(Users).order_by(Users.name.desc(), Users.id.asc()).all()
# 聚合與分組
session.query(func.max(Users.id), func.sum(Users.id)).group_by(Users.name).all()
session.query(func.max(Users.id)).group_by(Users.name).having(func.min(Users.id) > 2).all()
# 連表查詢
session.query(Users, Favor).filter(Users.id == Favor.nid).all()
session.query(Person).join(Favor).all() # 內連接
session.query(Person).join(Favor, isouter=True).all() # 左連接
# 集合運算
q1 = session.query(Users.name).filter(Users.id > 2)
q2 = session.query(Favor.caption).filter(Favor.nid < 2)
q1.union(q2).all() # UNION(去重)
q1.union_all(q2).all() # UNION ALL
實際開發中,可根據專案規模與團隊習慣選擇合適方案,甚至在同一專案中混合使用:核心業務用 ORM,複雜報表查詢用 Core 或原生 SQL。