Python ORM 选型:SQLAlchemy vs Tortoise-ORM vs Peewee

Python 的 ORM 生态比较分散,没有像 Rails ActiveRecord 那样一统天下的选择。这篇文章对比一下我用过的几个。 SQLAlchemy:工业级首选 SQLAlchemy 是 Python 生态里最成熟、功能最强的 ORM,分两层: Core:SQL 表达式语言,接近裸 SQL ORM:高级对象映射层 from sqlalchemy import create_engine, Column, Integer, String, ForeignKey from sqlalchemy.orm import DeclarativeBase, relationship, Session engine = create_engine("mysql+pymysql://user:pass@localhost/mydb") class Base(DeclarativeBase): pass class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True) name = Column(String(50), nullable=False) email = Column(String(100), unique=True) orders = relationship("Order", back_populates="user") class Order(Base): __tablename__ = "orders" id = Column(Integer, primary_key=True) user_id = Column(Integer, ForeignKey("users.id")) total = Column(Integer) user = relationship("User", back_populates="orders") # 查询 with Session(engine) as session: users = session.query(User).filter(User.name.like("K%")).all() # 预加载关联数据(避免 N+1 问题) from sqlalchemy.orm import selectinload users = session.query(User).options(selectinload(User.orders)).all() SQLAlchemy 2.0 新语法(推荐): from sqlalchemy import select with Session(engine) as session: stmt = select(User).where(User.name.like("K%")) users = session.execute(stmt).scalars().all() 优点:功能极强,支持复杂查询,文档完善,生态成熟。 缺点:学习曲线陡,概念多(Session、Unit of Work、lazy loading……)。 Tortoise-ORM:异步场景的选择 专为 asyncio 设计,和 FastAPI 配合很顺手: ...

2022-07-19 · 2 min · Kada Liao

MySQL 慢查询排查:从发现到解决

线上接口突然变慢,大概率是数据库查询出了问题。这篇文章梳理一下排查慢查询的完整流程。 开启慢查询日志 -- 查看当前配置 SHOW VARIABLES LIKE 'slow_query%'; SHOW VARIABLES LIKE 'long_query_time'; -- 临时开启(重启失效) SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1; -- 超过 1 秒的查询记录 SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log'; -- 永久配置(写入 my.cnf) [mysqld] slow_query_log = 1 slow_query_log_file = /var/log/mysql/slow.log long_query_time = 1 log_queries_not_using_indexes = 1 # 记录未使用索引的查询 用 pt-query-digest 分析 手动看慢查询日志很费时,pt-query-digest(Percona Toolkit)可以聚合分析: pt-query-digest /var/log/mysql/slow.log | head -100 输出会按响应时间降序列出各类查询,告诉你哪些查询最值得优化: # Query 1: 0.50 QPS, 2.50x concurrency, ID 0xABC123 # This item is included in the report because it matches --limit. # pct total min max avg 95% stddev median # Count 15 1000 # Exec time 80% 200s 0.1s 5s 0.2s 0.5s 0.3s 0.15s # Rows sent 5% 500 0 10 0 1 1 0 # Rows examine 90% 900k 100 5000 900 2000 800 600 重点关注 Rows examine(扫描行数)和 Exec time(执行时间)。 ...

2022-04-10 · 2 min · Kada Liao

FastAPI 入门:用 Python 快速构建现代 Web API

Django REST Framework 太重,Flask 太裸。FastAPI 在这两者之间找到了一个很好的平衡点。 为什么是 FastAPI 快:基于 Starlette,性能接近 Node.js 自动文档:自动生成 Swagger UI 和 ReDoc 类型驱动:用 Pydantic 做数据校验,写一次模型,校验和文档都有了 原生异步:完整支持 async/await 5 分钟上手 uv pip install fastapi uvicorn # main.py from fastapi import FastAPI app = FastAPI(title="我的 API", version="1.0.0") @app.get("/") def read_root(): return {"message": "Hello, World!"} @app.get("/users/{user_id}") def get_user(user_id: int, include_orders: bool = False): return {"user_id": user_id, "include_orders": include_orders} uvicorn main:app --reload # 访问 http://localhost:8000/docs 看自动生成的文档 Pydantic 模型:请求/响应的核心 from pydantic import BaseModel, EmailStr, Field from datetime import datetime class UserCreate(BaseModel): name: str = Field(..., min_length=2, max_length=50) email: EmailStr age: int = Field(..., ge=0, le=150) class UserResponse(BaseModel): id: int name: str email: str created_at: datetime class Config: from_attributes = True # 支持从 ORM 对象创建 @app.post("/users", response_model=UserResponse, status_code=201) def create_user(user: UserCreate): # FastAPI 自动解析请求体、校验字段、返回时过滤多余字段 db_user = save_to_db(user) return db_user Pydantic 的 Field 支持的校验相当丰富:字符串长度、数字范围、正则表达式……不需要手写校验逻辑。 ...

2022-03-28 · 2 min · Kada Liao

RESTful API 设计规范:我总结的一些原则

在做了几年后端开发之后,我发现 API 设计的好坏对前后端协作效率影响很大。这篇文章把我总结的一些原则写下来。 URL 设计 用名词,不用动词 # 不好 GET /getUser POST /createOrder DELETE /deleteProduct?id=1 # 好 GET /users/{id} POST /orders DELETE /products/{id} 层级关系用路径表达 GET /users/{userId}/orders # 某个用户的所有订单 GET /users/{userId}/orders/{id} # 某个用户的某个订单 用复数名词 GET /users # 不是 /user GET /products # 不是 /product HTTP 方法的语义 方法 语义 幂等 GET 读取资源 是 POST 创建资源 否 PUT 全量替换资源 是 PATCH 部分更新资源 否 DELETE 删除资源 是 幂等意味着重复调用和调用一次效果相同,这对网络重试很重要。 状态码要用对 200 OK - 成功 201 Created - 创建成功(POST 之后返回) 204 No Content - 成功但无响应体(DELETE 常用) 400 Bad Request - 客户端参数错误 401 Unauthorized - 未认证(没登录) 403 Forbidden - 无权限(登录了但没权限) 404 Not Found - 资源不存在 409 Conflict - 资源冲突(如重复创建) 422 Unprocessable Entity - 参数格式正确但业务校验失败 500 Internal Server Error - 服务器内部错误 最常见的错误:把所有错误都返回 200,在响应体里用 code 区分。这让客户端必须解析响应体才能判断是否成功,无法用 HTTP 层面的工具(代理、监控)做处理。 ...

2021-07-14 · 1 min · Kada Liao

MySQL 事务、锁与死锁排查

事务和锁是 MySQL 里最复杂也最容易出问题的部分。这篇文章从实际问题出发,梳理核心概念和排查思路。 事务隔离级别 MySQL 有四种隔离级别,对应不同的并发问题: 隔离级别 脏读 不可重复读 幻读 READ UNCOMMITTED ✓ 可能 ✓ 可能 ✓ 可能 READ COMMITTED ✗ 不会 ✓ 可能 ✓ 可能 REPEATABLE READ(默认) ✗ 不会 ✗ 不会 部分解决 SERIALIZABLE ✗ 不会 ✗ 不会 ✗ 不会 InnoDB 默认是 REPEATABLE READ,并且通过 MVCC + Gap Lock 解决了大部分幻读问题。 InnoDB 的锁类型 行锁(最常用): 共享锁(S 锁):SELECT ... LOCK IN SHARE MODE,允许多个事务同时读 排他锁(X 锁):SELECT ... FOR UPDATE 或增删改,同一时间只有一个事务能持有 意向锁:表级别的锁,表示"我将要对某些行加行锁",用于快速判断表级操作是否冲突。 Gap Lock(间隙锁):锁定索引记录之间的间隙,防止插入新记录,解决幻读。 Next-Key Lock:行锁 + Gap Lock 的组合,InnoDB 在 REPEATABLE READ 下默认使用。 ...

2020-10-25 · 2 min · Kada Liao

MySQL 索引原理与优化实战

面试必考,工作必用。索引这个话题说简单也简单,说复杂也复杂。这篇文章尝试把最实用的部分说清楚。 B+ 树索引的工作原理 MySQL InnoDB 的索引底层是 B+ 树。B+ 树的特点: 所有数据存在叶子节点 叶子节点之间用链表相连(支持范围查询) 非叶子节点只存键值,不存数据(让每层能存更多节点) 这意味着一次查询最多只需要走 树高 次 IO。对于千万级的表,B+ 树高度通常只有 3-4 层,也就是 3-4 次 IO 就能找到数据。 聚簇索引 vs 二级索引 聚簇索引(主键索引):叶子节点直接存行数据。 二级索引(普通索引):叶子节点存的是主键值,查到后还需要回表(再走一次聚簇索引)。 -- 走二级索引,需要回表 SELECT * FROM users WHERE name = 'Kada'; -- 覆盖索引,不需要回表(索引包含了所有需要的字段) SELECT id, name FROM users WHERE name = 'Kada'; 覆盖索引是避免回表的常用技巧,查询的字段都在索引里,就不用回表了。 联合索引的最左前缀原则 -- 建了联合索引 (a, b, c) CREATE INDEX idx_abc ON t (a, b, c); -- 能用到索引 WHERE a = 1 WHERE a = 1 AND b = 2 WHERE a = 1 AND b = 2 AND c = 3 -- 不能用到索引(跳过了 a) WHERE b = 2 WHERE c = 3 WHERE b = 2 AND c = 3 联合索引按最左前缀匹配,中间不能断。 ...

2020-06-18 · 2 min · Kada Liao