<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>数据与 ORM on 扎塔-Zata</title><link>https://www.zata.cc/tags/%E6%95%B0%E6%8D%AE%E4%B8%8E-orm/</link><description>Recent content in 数据与 ORM on 扎塔-Zata</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>Example Person</copyright><lastBuildDate>Thu, 24 Sep 2026 17:09:06 +0800</lastBuildDate><atom:link href="https://www.zata.cc/tags/%E6%95%B0%E6%8D%AE%E4%B8%8E-orm/index.xml" rel="self" type="application/rss+xml"/><item><title>Alembic</title><link>https://www.zata.cc/p/alembic/</link><pubDate>Fri, 12 Sep 2025 17:43:01 +0800</pubDate><guid>https://www.zata.cc/p/alembic/</guid><description>&lt;img src="https://www.zata.cc/p/alembic/images/index/index.png" alt="Featured image of post Alembic" />&lt;p>常用命令&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># === Alembic 核心命令速查 ===&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. 初始化&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 作用：在项目中创建 Alembic 环境 (alembic.ini, env.py 等)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">alembic init &amp;lt;alembic目录名&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. 生成迁移脚本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 作用：对比模型与数据库差异，自动生成迁移文件&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">alembic revision -m &lt;span class="s2">&amp;#34;简短的描述&amp;#34;&lt;/span> --autogenerate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. 执行升级 (应用迁移)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 作用：将数据库更新到最新版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">alembic upgrade head
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 作用：升级到指定版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">alembic upgrade &amp;lt;版本号&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 作用：向前升级 1 个版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">alembic upgrade +1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 4. 执行降级 (回滚迁移)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 作用：将数据库回滚 1 个版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">alembic downgrade -1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 作用：回滚到指定版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">alembic downgrade &amp;lt;版本号&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 作用：回滚到最初始 (空) 状态&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">alembic downgrade base
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 5. 查看状态&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 作用：查看当前数据库所在的版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">alembic current
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 作用：查看所有迁移历史&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">alembic &lt;span class="nb">history&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 6. 标记版本 (不执行SQL)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 作用：将数据库“欺骗”为已处于某个版本 (常用于修复错误状态)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">alembic stamp head
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">alembic stamp &amp;lt;版本号&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 7. 查看迁移内容&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 作用：显示某个迁移版本将执行的 SQL (预览)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">alembic show &amp;lt;版本号或head&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="什么是-alembic">什么是 Alembic？
&lt;/h3>&lt;p>Alembic 是一个为 &lt;a class="link" href="https://www.sqlalchemy.org/" target="_blank" rel="noopener"
>SQLAlchemy&lt;/a> 设计的轻量级数据库迁移工具。在软件开发中，特别是项目迭代过程中，数据库的结构（Schema）几乎不可避免地会发生变化，例如添加新表、增加字段、修改数据类型等。直接在生产数据库上手动修改 SQL 是一件风险很高且难以追踪和协作的事情。&lt;/p>
&lt;p>Alembic 的出现就是为了解决这个问题。它允许你：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>版本化管理数据库结构&lt;/strong>：就像使用 Git 管理代码一样，Alembic 可以为你的数据库结构的每一次变更创建一个版本。&lt;/li>
&lt;li>&lt;strong>生成迁移脚本&lt;/strong>：它可以自动或半自动地检测你的 SQLAlchemy 模型（Models）与当前数据库结构之间的差异，并生成相应的 Python 迁移脚本。&lt;/li>
&lt;li>&lt;strong>轻松升级和降级&lt;/strong>：你可以通过简单的命令，将数据库结构“升级”到最新版本，或者“降级”到任意一个旧版本。&lt;/li>
&lt;li>&lt;strong>团队协作&lt;/strong>：团队成员可以共享迁移脚本，确保每个人的开发环境和最终的生产环境数据库结构保持一致。&lt;/li>
&lt;li>&lt;strong>保证数据安全&lt;/strong>：它提供了在迁移过程中处理现有数据的能力。&lt;/li>
&lt;/ul>
&lt;p>简单来说，&lt;strong>Alembic 就是连接你的应用程序代码（通过 SQLAlchemy 模型定义）和数据库实际状态之间的桥梁，并用版本化的脚本来管理这种变化。&lt;/strong>&lt;/p>
&lt;h3 id="alembic-核心概念">Alembic 核心概念
&lt;/h3>&lt;p>在开始使用之前，理解几个核心概念至关重要：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Migration (迁移)&lt;/strong>：指从一个数据库结构版本变为另一个版本的操作。&lt;/li>
&lt;li>&lt;strong>Revision (版本号)&lt;/strong>：Alembic 会为每一次迁移生成一个唯一的、有序的版本号（一个哈希字符串），用于标识数据库的特定状态。&lt;/li>
&lt;li>&lt;strong>Migration Script (迁移脚本)&lt;/strong>：一个包含 &lt;code>upgrade()&lt;/code> 和 &lt;code>downgrade()&lt;/code> 两个函数的 Python 文件。&lt;code>upgrade()&lt;/code> 函数定义了如何将数据库“升级”到这个版本，而 &lt;code>downgrade()&lt;/code> 则定义了如何“降级”回上一个版本。&lt;/li>
&lt;li>&lt;strong>Environment (环境)&lt;/strong>：Alembic 的运行环境。它由 &lt;code>alembic.ini&lt;/code> 配置文件和 &lt;code>env.py&lt;/code> 脚本文件定义，用于配置数据库连接、SQLAlchemy 模型的元数据等。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="alembic-使用教程">Alembic 使用教程
&lt;/h3>&lt;p>下面我们将通过一个实际的例子，一步步带你入门 Alembic。&lt;/p>
&lt;h4 id="第一步安装">第一步：安装
&lt;/h4>&lt;p>首先，确保你已经安装了 SQLAlchemy 和 Alembic。如果还没有，可以通过 uv或者 pip 安装：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">uv add sqlalchemy almbic
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip install sqlalchemy alembic
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>同时，你需要为你想要连接的数据库安装相应的驱动。例如，对于 PostgreSQL，你可能需要安装 &lt;code>psycopg2-binary&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install psycopg2-binary
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果是Sqlite的话，就不需要安装驱动&lt;/p>
&lt;h4 id="第二步初始化-alembic-环境">第二步：初始化 Alembic 环境
&lt;/h4>&lt;p>在一个新的项目目录中，运行以下命令来创建一个 Alembic &amp;ldquo;迁移仓库&amp;rdquo;：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">alembic init alembic
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这个命令会创建一个名为 &lt;code>alembic&lt;/code> 的文件夹和一个 &lt;code>alembic.ini&lt;/code> 配置文件。目录结构如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">your_project/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── alembic/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── versions/ # 存放所有迁移脚本的地方
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── script.py.mako # 生成迁移脚本的模板
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── env.py # 配置和运行Alembic环境的脚本
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── alembic.ini # Alembic 的主配置文件
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="第三步配置数据库连接">第三步：配置数据库连接
&lt;/h4>&lt;p>打开 &lt;code>alembic.ini&lt;/code> 文件，找到 &lt;code>sqlalchemy.url&lt;/code> 这一行，并修改它以指向你的数据库。例如，使用 Sqlite：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># atexample.ini&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">sqlalchemy.url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">sqlite:///./resources/app.db&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>页可以在env.py文件中进行配置&amp;rsquo;config.set_main_option(&amp;ldquo;sqlalchemy.url&amp;rdquo;, settings.database_url) '&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">alembic&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">context&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Add the project root to the path to import our app modules&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">insert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dirname&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dirname&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__file__&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Import our database configuration and models&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">app.core.database&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">app.core.config&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">settings&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">app.models&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">User&lt;/span> &lt;span class="c1"># Import models for autogenerate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># this is the Alembic Config object, which provides&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># access to the values within the .ini file in use.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">config&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Set the database URL from our configuration&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">set_main_option&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;sqlalchemy.url&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">settings&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">database_url&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="第四步关联-sqlalchemy-模型">第四步：关联 SQLAlchemy 模型
&lt;/h4>&lt;p>现在，需要让 Alembic知道你的 SQLAlchemy 模型定义在哪里，以便进行比较。&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>创建你的模型&lt;/strong>：
假设你在项目根目录下有一个 &lt;code>models.py&lt;/code> 文件，内容如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># models.py&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">sqlalchemy&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Column&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">String&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">sqlalchemy.ext.declarative&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">declarative_base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Base&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">declarative_base&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">User&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Base&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">__tablename__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;users&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">primary_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">email&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># 假设我们稍后会把这个字段设为非空&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>配置 &lt;code>env.py&lt;/code>&lt;/strong>：
打开 &lt;code>alembic/env.py&lt;/code> 文件，进行以下修改：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>导入你的模型 &lt;code>Base&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 在文件顶部&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">my_project.models&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Base&lt;/span> &lt;span class="c1"># 假设你的项目名叫 my_project&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>找到 &lt;code>target_metadata = None&lt;/code> 这一行，并将其修改为：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 指向你的模型的 Base.metadata&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">target_metadata&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Base&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">metadata&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;p>这步操作告诉 Alembic：“请以 &lt;code>Base.metadata&lt;/code> 中定义的模型作为最终的数据库结构标准。”&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h4 id="第五步创建第一个迁移脚本">第五步：创建第一个迁移脚本
&lt;/h4>&lt;p>现在，你的数据库还是空的，而你的代码中已经定义了一个 &lt;code>User&lt;/code> 模型。让我们来生成第一个迁移脚本，以在数据库中创建 &lt;code>users&lt;/code> 表。&lt;/p>
&lt;p>运行以下命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">alembic revision --autogenerate -m &lt;span class="s2">&amp;#34;Create users table&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>revision&lt;/code>: 创建一个新的版本脚本。&lt;/li>
&lt;li>&lt;code>--autogenerate&lt;/code>: 这个是 Alembic 的核心功能之一。它会自动检测 &lt;code>target_metadata&lt;/code> (你的模型) 和当前数据库之间的差异，并自动生成迁移代码。&lt;/li>
&lt;li>&lt;code>-m &amp;quot;...&amp;quot;&lt;/code>: 为这次迁移添加一段简短的描述信息。&lt;/li>
&lt;/ul>
&lt;p>执行成功后，你会在 &lt;code>alembic/versions/&lt;/code> 目录下看到一个新的 Python 文件，文件名类似 &lt;code>xxxxxxxxxxxx_create_users_table.py&lt;/code>。打开它，内容大致如下：&lt;/p>
&lt;p>&lt;strong>需要非常注意的一点是，upgrade和downgrade函数需要再次检查一下，因为会生成错误，例如，想要设置默认的数值，需要在生成之后添加server_default=&amp;lsquo;0&amp;rsquo;参数&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Create users table
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">Revision ID: xxxxxxxxxxxx
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">Revises:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">Create Date: 2023-10-27 10:00:00.000000
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Sequence&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Union&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">alembic&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">op&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sqlalchemy&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">sa&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># revision identifiers, used by Alembic.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">revision&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;xxxxxxxxxxxx&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">down_revision&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Union&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">branch_labels&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Union&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Sequence&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">depends_on&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Union&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Sequence&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">upgrade&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># ### commands auto generated by Alembic - please adjust! ###&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">op&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_table&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;users&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sa&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;id&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">sa&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sa&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;name&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">sa&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">length&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sa&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;email&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">sa&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">length&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sa&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">PrimaryKeyConstraint&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;id&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># ### end Alembic commands ###&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">downgrade&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># ### commands auto generated by Alembic - please adjust! ###&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">op&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">drop_table&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;users&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># ### end Alembic commands ###&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>upgrade()&lt;/code> 函数包含了创建 &lt;code>users&lt;/code> 表的代码，而 &lt;code>downgrade()&lt;/code> 则包含了删除该表的代码。&lt;/p>
&lt;h4 id="第六步应用迁移-升级数据库">第六步：应用迁移 (升级数据库)
&lt;/h4>&lt;p>现在，我们来执行这个迁移脚本，真正地在数据库中创建这张表。&lt;/p>
&lt;p>运行以下命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">alembic upgrade head
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>upgrade&lt;/code>: 执行升级操作。&lt;/li>
&lt;li>&lt;code>head&lt;/code>: &lt;code>head&lt;/code> 是一个特殊的标识符，代表最新的版本。这条命令的意思是“将数据库升级到最新版本”。&lt;/li>
&lt;/ul>
&lt;p>执行后，连接到你的数据库，你会发现 &lt;code>users&lt;/code> 表已经被成功创建了。同时，数据库中还会多出一张名为 &lt;code>alembic_version&lt;/code> 的表，它用来记录当前数据库的版本号。&lt;/p>
&lt;h4 id="第七步进行模型变更并再次迁移">第七步：进行模型变更并再次迁移
&lt;/h4>&lt;p>项目继续开发，需求变更，我们需要给 &lt;code>User&lt;/code> 模型增加一个 &lt;code>fullname&lt;/code> 字段，并且把 &lt;code>email&lt;/code> 字段设置为非空。&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>修改模型&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># models.py&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">User&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Base&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">__tablename__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;users&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">primary_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">fullname&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># 新增字段&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">email&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 修改为非空&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>生成新的迁移脚本&lt;/strong>：
再次运行 &lt;code>autogenerate&lt;/code> 命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">alembic revision --autogenerate -m &lt;span class="s2">&amp;#34;Add fullname column and set email to non-nullable&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Alembic 会检测到模型的变动，并生成一个新的迁移脚本。内容可能如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Add fullname column and set email to non-nullable
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">Revision ID: yyyyyyyyyyyy
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">Revises: xxxxxxxxxxxx
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">Create Date: 2023-10-27 10:10:00.000000
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">upgrade&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># ### commands auto generated by Alembic - please adjust! ###&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">op&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;users&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">sa&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;fullname&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">sa&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">length&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">op&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">alter_column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;users&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;email&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">existing_type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">sa&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">VARCHAR&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">length&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># ### end Alembic commands ###&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">downgrade&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># ### commands auto generated by Alembic - please adjust! ###&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">op&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">alter_column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;users&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;email&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">existing_type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">sa&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">VARCHAR&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">length&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">op&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">drop_column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;users&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;fullname&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># ### end Alembic commands ###&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>注意&lt;/strong>：将一个可空列改为不可空列时，如果表中已有数据且该列为 &lt;code>NULL&lt;/code>，直接升级会失败。&lt;code>autogenerate&lt;/code> 无法解决所有逻辑问题，有时需要你手动编辑迁移脚本，例如为已有行的 &lt;code>email&lt;/code> 列提供一个默认值。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>应用新的迁移&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">alembic upgrade head
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>执行后，&lt;code>users&lt;/code> 表的结构就会被更新。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h4 id="第八步降级数据库">第八步：降级数据库
&lt;/h4>&lt;p>如果你想撤销最近的一次迁移，可以执行降级操作。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 降级一个版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">alembic downgrade -1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 降级到指定的版本号&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">alembic downgrade yyyyyyyyyyyy
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 降级到最初始的状态（空数据库）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">alembic downgrade base
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="常用-alembic-命令总结">常用 Alembic 命令总结
&lt;/h3>&lt;ul>
&lt;li>&lt;code>alembic init &amp;lt;directory&amp;gt;&lt;/code>: 初始化 Alembic 仓库。&lt;/li>
&lt;li>&lt;code>alembic revision --autogenerate -m &amp;quot;message&amp;quot;&lt;/code>: 自动生成新的迁移脚本。&lt;/li>
&lt;li>&lt;code>alembic upgrade head&lt;/code>: 升级到最新版本。&lt;/li>
&lt;li>&lt;code>alembic upgrade +1&lt;/code>: 升级一个版本。&lt;/li>
&lt;li>&lt;code>alembic upgrade &amp;lt;revision&amp;gt;&lt;/code>: 升级到指定版本。&lt;/li>
&lt;li>&lt;code>alembic downgrade base&lt;/code>: 降级到最初始状态。&lt;/li>
&lt;li>&lt;code>alembic downgrade -1&lt;/code>: 降级一个版本。&lt;/li>
&lt;li>&lt;code>alembic downgrade &amp;lt;revision&amp;gt;&lt;/code>: 降级到指定版本之前。&lt;/li>
&lt;li>&lt;code>alembic history&lt;/code>: 显示迁移历史记录。&lt;/li>
&lt;li>&lt;code>alembic current&lt;/code>: 显示当前数据库的版本号。&lt;/li>
&lt;li>&lt;code>alembic stamp head&lt;/code>: 不执行迁移脚本，但将数据库的版本号标记为最新。这在手动修改数据库后，想让 Alembic 状态与数据库同步时很有用。&lt;/li>
&lt;/ul>
&lt;p>希望这份详细的介绍和教程能帮助你快速上手 Alembic！它是一个非常强大且实用的工具，在任何使用 SQLAlchemy 的项目中都值得引入。&lt;/p></description></item><item><title>SQLAlchemy简单入门</title><link>https://www.zata.cc/p/sqlalchemy%E7%AE%80%E5%8D%95%E5%85%A5%E9%97%A8/</link><pubDate>Tue, 11 Mar 2025 09:43:28 +0800</pubDate><guid>https://www.zata.cc/p/sqlalchemy%E7%AE%80%E5%8D%95%E5%85%A5%E9%97%A8/</guid><description>&lt;img src="https://www.zata.cc/p/sqlalchemy%E7%AE%80%E5%8D%95%E5%85%A5%E9%97%A8/images/index/index.png" alt="Featured image of post SQLAlchemy简单入门" />&lt;h2 id="基础使用">基础使用
&lt;/h2>&lt;p>SQLAlchemy 是 Python 中最流行的 ORM（对象关系映射）工具，用于操作数据库。它分为两部分：&lt;strong>SQLAlchemy Core&lt;/strong>（底层 SQL 操作）和 &lt;strong>SQLAlchemy ORM&lt;/strong>（对象映射）。本教程将涵盖两者的基础知识和示例。&lt;/p>
&lt;hr>
&lt;h3 id="1-安装-sqlalchemy">1. 安装 SQLAlchemy
&lt;/h3>&lt;p>首先，您需要安装 SQLAlchemy 和数据库驱动（例如 SQLite、MySQL 或 PostgreSQL）。这里以 SQLite 为例，因为它无需额外安装数据库服务器。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install sqlalchemy
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果使用 MySQL 或 PostgreSQL，需要安装对应的驱动：&lt;/p>
&lt;ul>
&lt;li>MySQL: &lt;code>pip install pymysql&lt;/code>&lt;/li>
&lt;li>PostgreSQL: &lt;code>pip install psycopg2&lt;/code>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="2-sqlalchemy-core-基础">2. SQLAlchemy Core 基础
&lt;/h3>&lt;p>SQLAlchemy Core 提供了一种接近原生 SQL 的方式来操作数据库。&lt;/p>
&lt;h4 id="21-连接数据库">2.1 连接数据库
&lt;/h4>&lt;p>首先，我们需要创建一个引擎（Engine）来连接数据库。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">sqlalchemy&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">create_engine&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># SQLite 数据库（文件存储在本地）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">engine&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_engine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;sqlite:///example.db&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">echo&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># echo=True 打印 SQL 日志&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="22-定义表结构">2.2 定义表结构
&lt;/h4>&lt;p>使用 &lt;code>Table&lt;/code> 对象定义数据库表。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">sqlalchemy&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Table&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Column&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">String&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">MetaData&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 创建元数据对象&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">metadata&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">MetaData&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 定义 users 表&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">users&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Table&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;users&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">metadata&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;id&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">primary_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;name&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">String&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;age&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Integer&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="23-创建表">2.3 创建表
&lt;/h4>&lt;p>将定义的表结构应用到数据库中。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 创建所有表&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">metadata&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_all&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">engine&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="24-插入数据">2.4 插入数据
&lt;/h4>&lt;p>使用 &lt;code>insert()&lt;/code> 方法插入数据。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">sqlalchemy&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">insert&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 插入单条数据&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">engine&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">connect&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">connection&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">stmt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">insert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">values&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;Alice&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">age&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">25&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">connection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">execute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">stmt&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">connection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">commit&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 插入多条数据&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">engine&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">connect&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">connection&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">stmt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">insert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">values&lt;/span>&lt;span class="p">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;name&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;Bob&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;age&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">30&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;name&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;Charlie&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;age&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">35&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">connection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">execute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">stmt&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">connection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">commit&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="25-查询数据">2.5 查询数据
&lt;/h4>&lt;p>使用 &lt;code>select()&lt;/code> 方法查询数据。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">sqlalchemy&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">select&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">engine&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">connect&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">connection&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">stmt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">select&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">where&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">age&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">25&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 查询年龄大于 25 的用户&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">connection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">execute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">stmt&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">row&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">row&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 输出：(id, name, age)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="26-更新和删除">2.6 更新和删除
&lt;/h4>&lt;p>更新和删除数据分别使用 &lt;code>update()&lt;/code> 和 &lt;code>delete()&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">sqlalchemy&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">update&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">delete&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">engine&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">connect&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">connection&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 更新&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">stmt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">update&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">where&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s1">&amp;#39;Alice&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">values&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">age&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">26&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">connection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">execute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">stmt&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">connection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">commit&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 删除&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">stmt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">delete&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">where&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s1">&amp;#39;Charlie&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">connection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">execute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">stmt&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">connection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">commit&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h3 id="3-sqlalchemy-orm-基础">3. SQLAlchemy ORM 基础
&lt;/h3>&lt;p>SQLAlchemy ORM 将数据库表映射为 Python 类，提供更面向对象的方式。&lt;/p>
&lt;h4 id="31-定义模型">3.1 定义模型
&lt;/h4>&lt;p>使用声明式基类（Declarative Base）定义表结构。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">sqlalchemy&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">create_engine&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Column&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">String&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">sqlalchemy.orm&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">declarative_base&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Session&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 创建基类&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Base&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">declarative_base&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 定义 User 类&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">User&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Base&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">__tablename__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;users&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">primary_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">age&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__repr__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;lt;User(name=&amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39;, age=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">age&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">)&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 创建引擎和表&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">engine&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_engine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;sqlite:///example_orm.db&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">echo&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Base&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">metadata&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_all&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">engine&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="32-添加数据">3.2 添加数据
&lt;/h4>&lt;p>使用会话（Session）操作数据。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 创建会话&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">Session&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">engine&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">session&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 添加单个用户&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">alice&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">User&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;Alice&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">age&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">25&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">alice&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 添加多个用户&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_all&lt;/span>&lt;span class="p">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">User&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;Bob&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">age&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">30&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">User&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;Charlie&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">age&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">35&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 提交事务&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">commit&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="33-查询数据">3.3 查询数据
&lt;/h4>&lt;p>使用 &lt;code>query&lt;/code> 或 &lt;code>select&lt;/code> 进行查询。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">Session&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">engine&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">session&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 查询所有用户&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">users&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">User&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">all&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">user&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">users&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 过滤查询&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">older_users&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">User&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">filter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">User&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">age&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">25&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">all&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">user&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">older_users&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="34-更新和删除">3.4 更新和删除
&lt;/h4>&lt;p>直接修改对象或使用 &lt;code>delete()&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">Session&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">engine&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">session&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 更新&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">alice&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">User&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">filter_by&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;Alice&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">first&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">alice&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">age&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">26&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">commit&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 删除&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">charlie&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">User&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">filter_by&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;Charlie&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">first&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">delete&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">charlie&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">commit&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h3 id="4-关系relationships">4. 关系（Relationships）
&lt;/h3>&lt;p>SQLAlchemy ORM 支持表之间的关系，例如一对多。&lt;/p>
&lt;h4 id="41-定义关系模型">4.1 定义关系模型
&lt;/h4>&lt;p>以下是一个用户（User）和地址（Address）的一对多关系示例。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">sqlalchemy&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ForeignKey&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">sqlalchemy.orm&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">relationship&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">User&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Base&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">__tablename__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;users&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">primary_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">addresses&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">relationship&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Address&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">back_populates&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;user&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 一对多关系&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Address&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Base&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">__tablename__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;addresses&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">primary_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">email&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">user_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ForeignKey&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;users.id&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">user&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">relationship&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;User&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">back_populates&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;addresses&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 多对一关系&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 创建表&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Base&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">metadata&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_all&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">engine&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="42-操作关系数据">4.2 操作关系数据
&lt;/h4>&lt;p>添加用户及其地址。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">Session&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">engine&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">session&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">user&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">User&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;Alice&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">user&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">addresses&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">Address&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">email&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;alice@example.com&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">Address&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">email&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;alice2@example.com&amp;#39;&lt;/span>&lt;span class="p">)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">commit&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 查询&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">alice&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">User&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">filter_by&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;Alice&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">first&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">alice&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">addresses&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 输出 Alice 的所有地址&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h3 id="5-高级功能">5. 高级功能
&lt;/h3>&lt;h4 id="51-事务管理">5.1 事务管理
&lt;/h4>&lt;p>SQLAlchemy 会话默认支持事务。如果需要手动控制：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">Session&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">engine&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">session&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">User&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;Test&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">commit&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">rollback&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Error: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="52-原生-sql">5.2 原生 SQL
&lt;/h4>&lt;p>如果需要执行原生 SQL：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">engine&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">connect&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">connection&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">connection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">execute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;SELECT * FROM users WHERE age &amp;gt; 25&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">row&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">row&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="53-连接池">5.3 连接池
&lt;/h4>&lt;p>SQLAlchemy 默认使用连接池管理数据库连接，可以通过 &lt;code>pool_size&lt;/code> 和 &lt;code>max_overflow&lt;/code> 配置：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">engine&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_engine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;sqlite:///example.db&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">pool_size&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">max_overflow&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h3 id="6-总结">6. 总结
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>Core&lt;/strong> 适合需要灵活 SQL 的场景。&lt;/li>
&lt;li>&lt;strong>ORM&lt;/strong> 适合面向对象编程，简化数据库操作。&lt;/li>
&lt;li>通过 &lt;code>engine&lt;/code>、&lt;code>metadata&lt;/code> 和 &lt;code>session&lt;/code>，SQLAlchemy 提供了强大的数据库操作能力。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="model类的使用">Model类的使用
&lt;/h2>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-py" data-lang="py">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Subscription&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Model&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">primary_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">500&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">category&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">check_frequency&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">last_checked&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">DateTime&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">last_updated&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">DateTime&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">status&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">20&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">default&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;active&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">created_at&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">DateTime&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">default&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">datetime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">utcnow&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Foreign keys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">user_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ForeignKey&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;user.id&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Relationships&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">selectors&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">relationship&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Selector&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">backref&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;subscription&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">lazy&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;dynamic&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cascade&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;all, delete-orphan&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">changes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">relationship&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Change&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">backref&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;subscription&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">lazy&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;dynamic&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cascade&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;all, delete-orphan&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__repr__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;&amp;lt;Subscription &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;gt;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>您提供的代码片段展示了一个使用 &lt;strong>Flask-SQLAlchemy&lt;/strong>（基于 SQLAlchemy 的扩展）定义的 &lt;code>Subscription&lt;/code> 模型类。这个类定义了一个数据库表结构，并包含了字段、外键和关系。以下是对这个类的详细解析：&lt;/p>
&lt;hr>
&lt;h3 id="前置条件">前置条件
&lt;/h3>&lt;ul>
&lt;li>&lt;code>db = SQLAlchemy()&lt;/code>：这是一个 Flask-SQLAlchemy 的实例，通常在 Flask 应用中初始化。它封装了 SQLAlchemy 的功能，并与 Flask 应用上下文绑定。&lt;/li>
&lt;li>需要导入必要的模块，例如：
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">flask_sqlalchemy&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">SQLAlchemy&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">datetime&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">datetime&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="类定义">类定义
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Subscription&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Model&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>Subscription&lt;/code> 是一个继承自 &lt;code>db.Model&lt;/code> 的类，&lt;code>db.Model&lt;/code> 是 Flask-SQLAlchemy 提供的基类，用于声明数据库模型。&lt;/li>
&lt;li>这个类会映射到数据库中的一个表，默认表名为类名的小写形式（&lt;code>subscription&lt;/code>）。如果想自定义表名，可以添加 &lt;code>__tablename__ = 'custom_name'&lt;/code>。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="字段columns">字段（Columns）
&lt;/h3>&lt;p>每个字段使用 &lt;code>db.Column&lt;/code> 定义，指定数据类型和约束条件。以下是逐一解析：&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>&lt;code>id = db.Column(db.Integer, primary_key=True)&lt;/code>&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>类型：&lt;code>Integer&lt;/code>（整数）。&lt;/li>
&lt;li>约束：&lt;code>primary_key=True&lt;/code> 表示这是主键，唯一标识每一行。通常会自动递增（SQLite 和 PostgreSQL 默认支持）。&lt;/li>
&lt;li>作用：作为表的主键，用于唯一标识每个订阅记录。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;code>name = db.Column(db.String(100), nullable=False)&lt;/code>&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>类型：&lt;code>String(100)&lt;/code>（长度最多 100 个字符的字符串）。&lt;/li>
&lt;li>约束：&lt;code>nullable=False&lt;/code> 表示该字段不能为空，必须提供值。&lt;/li>
&lt;li>作用：存储订阅的名称，例如“新闻订阅”。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;code>url = db.Column(db.String(500), nullable=False)&lt;/code>&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>类型：&lt;code>String(500)&lt;/code>（长度最多 500 个字符的字符串）。&lt;/li>
&lt;li>约束：&lt;code>nullable=False&lt;/code> 表示不能为空。&lt;/li>
&lt;li>作用：存储订阅的 URL，例如某个网站的地址。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;code>category = db.Column(db.String(50), nullable=False)&lt;/code>&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>类型：&lt;code>String(50)&lt;/code>（长度最多 50 个字符的字符串）。&lt;/li>
&lt;li>约束：&lt;code>nullable=False&lt;/code> 表示不能为空。&lt;/li>
&lt;li>作用：存储订阅的类别，例如“科技”或“娱乐”。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;code>check_frequency = db.Column(db.String(50), nullable=False)&lt;/code>&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>类型：&lt;code>String(50)&lt;/code>。&lt;/li>
&lt;li>约束：&lt;code>nullable=False&lt;/code> 表示不能为空。&lt;/li>
&lt;li>作用：存储检查频率，例如“daily”或“weekly”。（注：如果频率是固定的选项，可以考虑使用 &lt;code>db.Enum&lt;/code> 类型。）&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;code>last_checked = db.Column(db.DateTime, nullable=True)&lt;/code>&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>类型：&lt;code>DateTime&lt;/code>（日期时间类型）。&lt;/li>
&lt;li>约束：&lt;code>nullable=True&lt;/code> 表示可以为空。&lt;/li>
&lt;li>作用：记录上一次检查订阅的时间，可能为空（例如新创建时尚未检查）。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;code>last_updated = db.Column(db.DateTime, nullable=True)&lt;/code>&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>类型：&lt;code>DateTime&lt;/code>。&lt;/li>
&lt;li>约束：&lt;code>nullable=True&lt;/code> 表示可以为空。&lt;/li>
&lt;li>作用：记录订阅内容最后更新的时间，可能为空（例如未发生变化）。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;code>status = db.Column(db.String(20), default='active')&lt;/code>&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>类型：&lt;code>String(20)&lt;/code>。&lt;/li>
&lt;li>约束：&lt;code>default='active'&lt;/code> 表示默认值为“active”。&lt;/li>
&lt;li>作用：存储订阅的状态，例如“active”或“inactive”。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;code>created_at = db.Column(db.DateTime, default=datetime.utcnow)&lt;/code>&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>类型：&lt;code>DateTime&lt;/code>。&lt;/li>
&lt;li>约束：&lt;code>default=datetime.utcnow&lt;/code> 表示默认值为当前 UTC 时间（注意：这里使用的是函数 &lt;code>datetime.utcnow&lt;/code> 而不是 &lt;code>datetime.utcnow()&lt;/code>，因为 SQLAlchemy 需要一个可调用的对象）。&lt;/li>
&lt;li>作用：记录订阅创建的时间。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h3 id="外键foreign-key">外键（Foreign Key）
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">user_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ForeignKey&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;user.id&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>类型：&lt;code>Integer&lt;/code>。&lt;/li>
&lt;li>外键：&lt;code>db.ForeignKey('user.id')&lt;/code> 表示这是一个外键，引用 &lt;code>user&lt;/code> 表的 &lt;code>id&lt;/code> 列。&lt;/li>
&lt;li>约束：&lt;code>nullable=False&lt;/code> 表示不能为空，必须关联一个用户。&lt;/li>
&lt;li>作用：将 &lt;code>Subscription&lt;/code> 与 &lt;code>User&lt;/code> 表关联，表示每个订阅属于某个用户。&lt;/li>
&lt;/ul>
&lt;p>注意：这里假设存在一个 &lt;code>User&lt;/code> 模型类，定义了 &lt;code>id&lt;/code> 字段，例如：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">User&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Model&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">primary_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 其他字段...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h3 id="关系relationships">关系（Relationships）
&lt;/h3>&lt;p>SQLAlchemy ORM 支持表之间的关系，这里定义了两个一对多关系：&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>&lt;code>selectors = db.relationship('Selector', backref='subscription', lazy='dynamic', cascade='all, delete-orphan')&lt;/code>&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>目标&lt;/strong>：&lt;code>Selector&lt;/code> 是一个关联的模型类，表示“选择器”（可能用于指定订阅内容的提取规则）。&lt;/li>
&lt;li>&lt;strong>backref&lt;/strong>：&lt;code>'subscription'&lt;/code> 表示在 &lt;code>Selector&lt;/code> 类中可以通过 &lt;code>subscription&lt;/code> 属性反向访问对应的 &lt;code>Subscription&lt;/code> 对象。&lt;/li>
&lt;li>&lt;strong>lazy&lt;/strong>：&lt;code>'dynamic'&lt;/code> 表示返回一个动态查询对象（而不是直接加载所有数据），可以用 &lt;code>.all()&lt;/code> 或 &lt;code>.filter()&lt;/code> 等方法进一步操作。&lt;/li>
&lt;li>&lt;strong>cascade&lt;/strong>：&lt;code>'all, delete-orphan'&lt;/code> 表示所有操作（包括删除）都会级联到相关记录，且如果 &lt;code>Subscription&lt;/code> 被删除，所有关联的 &lt;code>Selector&lt;/code> 也会被删除（孤儿删除）。&lt;/li>
&lt;li>作用：一个订阅可以有多个选择器（一对多关系）。&lt;/li>
&lt;/ul>
&lt;p>假设的 &lt;code>Selector&lt;/code> 类：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Selector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Model&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">primary_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">subscription_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ForeignKey&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;subscription.id&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 其他字段...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;code>changes = db.relationship('Change', backref='subscription', lazy='dynamic', cascade='all, delete-orphan')&lt;/code>&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>目标&lt;/strong>：&lt;code>Change&lt;/code> 表示“变更”记录（可能用于跟踪订阅内容的更改）。&lt;/li>
&lt;li>&lt;strong>backref&lt;/strong>：&lt;code>'subscription'&lt;/code> 允许从 &lt;code>Change&lt;/code> 反向访问 &lt;code>Subscription&lt;/code>。&lt;/li>
&lt;li>&lt;strong>lazy&lt;/strong>：&lt;code>'dynamic'&lt;/code> 同上。&lt;/li>
&lt;li>&lt;strong>cascade&lt;/strong>：&lt;code>'all, delete-orphan'&lt;/code> 同上。&lt;/li>
&lt;li>作用：一个订阅可以有多个变更记录（一对多关系）。&lt;/li>
&lt;/ul>
&lt;p>假设的 &lt;code>Change&lt;/code> 类：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Change&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Model&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">primary_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">subscription_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ForeignKey&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;subscription.id&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 其他字段...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h3 id="字符串表示方法">字符串表示方法
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="fm">__repr__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;&amp;lt;Subscription &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;gt;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>定义了对象的字符串表示形式。&lt;/li>
&lt;li>调用 &lt;code>print(subscription)&lt;/code> 时会显示类似 &lt;code>&amp;lt;Subscription News&amp;gt;&lt;/code> 的输出，便于调试。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="整体功能解析">整体功能解析
&lt;/h3>&lt;p>&lt;code>Subscription&lt;/code> 类描述了一个订阅系统中的核心实体，可能用于一个订阅管理或内容监控应用：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>字段&lt;/strong>：记录订阅的基本信息（名称、URL、类别等）、状态和时间戳。&lt;/li>
&lt;li>&lt;strong>外键&lt;/strong>：通过 &lt;code>user_id&lt;/code> 与用户关联，确保每个订阅属于某个用户。&lt;/li>
&lt;li>&lt;strong>关系&lt;/strong>：
&lt;ul>
&lt;li>&lt;code>selectors&lt;/code>：允许为订阅定义多个选择器（例如 CSS 选择器或 XPath，用于提取网页内容）。&lt;/li>
&lt;li>&lt;code>changes&lt;/code>：记录订阅内容的变更历史。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>级联删除&lt;/strong>：当订阅被删除时，关联的 &lt;code>selectors&lt;/code> 和 &lt;code>changes&lt;/code> 也会自动删除。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="示例用法">示例用法
&lt;/h3>&lt;p>假设在 Flask 应用中初始化并使用这个模型：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">flask&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Flask&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">flask_sqlalchemy&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">SQLAlchemy&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Flask&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__name__&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;SQLALCHEMY_DATABASE_URI&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;sqlite:///app.db&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">db&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">SQLAlchemy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">app&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 定义模型（如上所述）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 创建表&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">app&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">app_context&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_all&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 添加数据&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">app&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">app_context&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">new_subscription&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Subscription&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;Tech News&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;https://example.com&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">category&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;Tech&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">check_frequency&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;daily&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">user_id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">new_subscription&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">commit&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 查询&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">app&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">app_context&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">subscription&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Subscription&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">filter_by&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;Tech News&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">first&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">subscription&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 输出: &amp;lt;Subscription Tech News&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">subscription&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">selectors&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">all&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="c1"># 查询关联的 selectors&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h3 id="注意事项">注意事项
&lt;/h3>&lt;ol>
&lt;li>&lt;strong>Flask 上下文&lt;/strong>：Flask-SQLAlchemy 需要在应用上下文中操作（如 &lt;code>with app.app_context():&lt;/code>）。&lt;/li>
&lt;li>&lt;strong>外键完整性&lt;/strong>：确保 &lt;code>user_id&lt;/code> 引用的 &lt;code>User&lt;/code> 记录存在，否则会抛出异常。&lt;/li>
&lt;li>&lt;strong>动态加载&lt;/strong>：&lt;code>lazy='dynamic'&lt;/code> 适合大数据量场景，但需要显式调用 &lt;code>.all()&lt;/code> 或其他方法来获取数据。&lt;/li>
&lt;/ol>
&lt;h2 id="sqlalchemy-relationships-教程理解数据库表之间的关系">SQLAlchemy Relationships 教程：理解数据库表之间的关系
&lt;/h2>&lt;p>在这篇教程中，我们将探讨如何使用 SQLAlchemy（一个流行的 Python ORM 工具）中的 &lt;code>db.relationship&lt;/code> 来定义和操作数据库表之间的关系。我们将以一个简单的订阅监控系统为例，逐步讲解代码，并展示如何通过关系字段简化数据库操作。&lt;/p>
&lt;p>当前日期是 &lt;strong>2025年3月10日&lt;/strong>，我们将使用最新版本的 SQLAlchemy 语法。&lt;/p>
&lt;hr>
&lt;h3 id="示例背景订阅监控系统">示例背景：订阅监控系统
&lt;/h3>&lt;p>假设我们正在构建一个系统，用于监控网页的变化。系统中包含以下三个表：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Subscription（订阅）&lt;/strong>：表示用户订阅的网页监控任务。&lt;/li>
&lt;li>&lt;strong>Selector（选择器）&lt;/strong>：定义订阅中要监控的网页元素（如某个 &lt;code>&amp;lt;div&amp;gt;&lt;/code> 或 &lt;code>&amp;lt;p&amp;gt;&lt;/code>）。&lt;/li>
&lt;li>&lt;strong>Change（变化）&lt;/strong>：记录选择器检测到的变化（如内容更新或元素出现/消失）。&lt;/li>
&lt;/ul>
&lt;p>这些表之间存在关联：&lt;/p>
&lt;ul>
&lt;li>一个 &lt;code>Subscription&lt;/code> 可以有多个 &lt;code>Selector&lt;/code> 和多个 &lt;code>Change&lt;/code>（一对多）。&lt;/li>
&lt;li>一个 &lt;code>Selector&lt;/code> 可以有多个 &lt;code>Change&lt;/code>（一对多）。&lt;/li>
&lt;li>&lt;code>Change&lt;/code> 属于某个 &lt;code>Subscription&lt;/code> 和某个 &lt;code>Selector&lt;/code>。&lt;/li>
&lt;/ul>
&lt;p>我们将使用 SQLAlchemy 的 &lt;code>db.relationship&lt;/code> 来定义这些关系。&lt;/p>
&lt;hr>
&lt;h3 id="模型代码">模型代码
&lt;/h3>&lt;p>以下是完整的模型定义，我们将在后面逐一讲解其中的 &lt;code>Relationships&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">app&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">db&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">datetime&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">datetime&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 订阅表&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Subscription&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Model&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">primary_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">500&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">category&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">check_frequency&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">last_checked&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">DateTime&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">last_updated&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">DateTime&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">status&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">20&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">default&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;active&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">created_at&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">DateTime&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">default&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">datetime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">utcnow&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 外键&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">user_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ForeignKey&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;user.id&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 关系字段&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">selectors&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">relationship&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Selector&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">backref&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;subscription&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">lazy&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;dynamic&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cascade&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;all, delete-orphan&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">changes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">relationship&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Change&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">backref&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;subscription&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">lazy&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;dynamic&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cascade&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;all, delete-orphan&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__repr__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;&amp;lt;Subscription &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;gt;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 选择器表&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Selector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Model&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">primary_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">selector_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">500&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">monitor_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># content_change, element_presence, attribute_change&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">keywords&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">500&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">last_content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Text&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">created_at&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">DateTime&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">default&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">datetime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">utcnow&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 外键&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">subscription_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ForeignKey&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;subscription.id&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__repr__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;&amp;lt;Selector &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1"> for &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">subscription_id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;gt;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 变化表&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Change&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Model&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">primary_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">selector_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ForeignKey&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;selector.id&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">old_content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Text&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">new_content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Text&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">change_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># content_change, element_appeared, element_disappeared, attribute_change&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">created_at&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">DateTime&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">default&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">datetime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">utcnow&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 外键&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">subscription_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ForeignKey&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;subscription.id&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">nullable&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 关系字段&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">selector&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">relationship&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Selector&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__repr__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;&amp;lt;Change &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1"> for &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">subscription_id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;gt;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h3 id="什么是-dbrelationship">什么是 &lt;code>db.relationship&lt;/code>？
&lt;/h3>&lt;p>&lt;code>db.relationship&lt;/code> 是 SQLAlchemy 提供的一种工具，用于定义数据库表之间的关系。它允许你：&lt;/p>
&lt;ul>
&lt;li>通过对象属性访问关联的数据，而无需手动编写 SQL 查询。&lt;/li>
&lt;li>支持一对多、多对一等关系。&lt;/li>
&lt;li>通过 &lt;code>backref&lt;/code> 参数实现双向访问。&lt;/li>
&lt;/ul>
&lt;p>在我们的代码中，&lt;code>db.relationship&lt;/code> 连接了 &lt;code>Subscription&lt;/code>、&lt;code>Selector&lt;/code> 和 &lt;code>Change&lt;/code> 三个表。&lt;/p>
&lt;hr>
&lt;h3 id="逐步解析代码中的-relationships">逐步解析代码中的 &lt;code>Relationships&lt;/code>
&lt;/h3>&lt;h4 id="subscription-中的关系">&lt;code>Subscription&lt;/code> 中的关系
&lt;/h4>&lt;p>在 &lt;code>Subscription&lt;/code> 类中定义了两个关系字段：&lt;/p>
&lt;h5 id="selectors--dbrelationshipselector-backrefsubscription-lazydynamic-cascadeall-delete-orphan">&lt;code>selectors = db.relationship('Selector', backref='subscription', lazy='dynamic', cascade='all, delete-orphan')&lt;/code>
&lt;/h5>&lt;ul>
&lt;li>&lt;strong>含义&lt;/strong>：
&lt;ul>
&lt;li>&lt;code>Subscription&lt;/code> 和 &lt;code>Selector&lt;/code> 是一对多关系。一个订阅可以有多个选择器。&lt;/li>
&lt;li>&lt;code>Selector&lt;/code> 表中的 &lt;code>subscription_id&lt;/code> 是外键，指向 &lt;code>Subscription&lt;/code> 的 &lt;code>id&lt;/code>。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>参数解析&lt;/strong>：
&lt;ul>
&lt;li>&lt;code>'Selector'&lt;/code>：关联的目标模型类。&lt;/li>
&lt;li>&lt;code>backref='subscription'&lt;/code>：在 &lt;code>Selector&lt;/code> 中生成一个反向引用属性 &lt;code>subscription&lt;/code>，允许从 &lt;code>Selector&lt;/code> 访问其所属的 &lt;code>Subscription&lt;/code>。&lt;/li>
&lt;li>&lt;code>lazy='dynamic'&lt;/code>：延迟加载，返回一个动态查询对象，需要手动调用 &lt;code>.all()&lt;/code> 或其他方法获取数据。&lt;/li>
&lt;li>&lt;code>cascade='all, delete-orphan'&lt;/code>：当 &lt;code>Subscription&lt;/code> 被删除时，其所有 &lt;code>Selector&lt;/code> 也会被删除；如果某个 &lt;code>Selector&lt;/code> 失去了与 &lt;code>Subscription&lt;/code> 的关联，也会被删除。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>用法示例&lt;/strong>：
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">sub&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Subscription&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 获取 ID 为 1 的订阅&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">all_selectors&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">sub&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">selectors&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">all&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 获取所有相关选择器&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">selector&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">all_selectors&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">selector&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;h5 id="changes--dbrelationshipchange-backrefsubscription-lazydynamic-cascadeall-delete-orphan">&lt;code>changes = db.relationship('Change', backref='subscription', lazy='dynamic', cascade='all, delete-orphan')&lt;/code>
&lt;/h5>&lt;ul>
&lt;li>&lt;strong>含义&lt;/strong>：
&lt;ul>
&lt;li>&lt;code>Subscription&lt;/code> 和 &lt;code>Change&lt;/code> 是一对多关系。一个订阅可以有多个变化记录。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>参数解析&lt;/strong>：
&lt;ul>
&lt;li>同上，&lt;code>backref&lt;/code> 和 &lt;code>cascade&lt;/code> 的作用类似。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>用法示例&lt;/strong>：
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">sub&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Subscription&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">all_changes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">sub&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">changes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">all&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 获取所有变化记录&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">change&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">all_changes&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Change: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">change&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">old_content&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> -&amp;gt; &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">change&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new_content&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h4 id="change-中的关系">&lt;code>Change&lt;/code> 中的关系
&lt;/h4>&lt;h5 id="selector--dbrelationshipselector">&lt;code>selector = db.relationship('Selector')&lt;/code>
&lt;/h5>&lt;ul>
&lt;li>&lt;strong>含义&lt;/strong>：
&lt;ul>
&lt;li>&lt;code>Change&lt;/code> 和 &lt;code>Selector&lt;/code> 是一对多关系。一个选择器可以有多个变化记录。&lt;/li>
&lt;li>&lt;code>Change&lt;/code> 表中的 &lt;code>selector_id&lt;/code> 是外键，指向 &lt;code>Selector&lt;/code> 的 &lt;code>id&lt;/code>。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>参数解析&lt;/strong>：
&lt;ul>
&lt;li>&lt;code>'Selector'&lt;/code>：关联的目标模型类。&lt;/li>
&lt;li>默认 &lt;code>lazy='select'&lt;/code>：在访问时自动加载关联的 &lt;code>Selector&lt;/code> 对象。&lt;/li>
&lt;li>没有 &lt;code>backref&lt;/code>，因此无法从 &lt;code>Selector&lt;/code> 直接访问其所有 &lt;code>Change&lt;/code>（需要手动查询）。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>用法示例&lt;/strong>：
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">change&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Change&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 获取 ID 为 1 的变化记录&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">related_selector&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">change&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">selector&lt;/span> &lt;span class="c1"># 获取对应的选择器&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">related_selector&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="relationships-的核心作用">&lt;code>Relationships&lt;/code> 的核心作用
&lt;/h3>&lt;p>通过 &lt;code>db.relationship&lt;/code>，我们可以：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>简化数据访问&lt;/strong>：
&lt;ul>
&lt;li>无需编写复杂的 SQL JOIN 查询，直接通过对象属性访问关联数据。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>支持双向导航&lt;/strong>：
&lt;ul>
&lt;li>例如，从 &lt;code>Subscription&lt;/code> 到 &lt;code>Selector&lt;/code>（&lt;code>sub.selectors&lt;/code>），或从 &lt;code>Selector&lt;/code> 到 &lt;code>Subscription&lt;/code>（&lt;code>selector.subscription&lt;/code>）。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>自动管理外键&lt;/strong>：
&lt;ul>
&lt;li>创建对象时，SQLAlchemy 会自动处理外键赋值。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>级联操作&lt;/strong>：
&lt;ul>
&lt;li>删除 &lt;code>Subscription&lt;/code> 时，其所有 &lt;code>Selector&lt;/code> 和 &lt;code>Change&lt;/code> 也会被删除。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h3 id="实战示例">实战示例
&lt;/h3>&lt;p>让我们通过一个完整示例展示 &lt;code>Relationships&lt;/code> 的用法。&lt;/p>
&lt;h4 id="创建数据">创建数据
&lt;/h4>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 创建一个订阅&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sub&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Subscription&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Tech News&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://example.com/news&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">category&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;News&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">check_frequency&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;daily&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">user_id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 创建两个选择器&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">selector1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Selector&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Headline&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">selector_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;div.headline&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">monitor_type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;content_change&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">subscription&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">sub&lt;/span> &lt;span class="c1"># 自动关联&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">selector2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Selector&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Summary&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">selector_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;p.summary&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">monitor_type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;content_change&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">subscription&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">sub&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 创建一个变化记录&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">change&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Change&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">selector_id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">selector1&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">old_content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Old Headline&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">new_content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;New Headline&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">change_type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;content_change&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">subscription&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">sub&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 保存到数据库&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sub&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">commit&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="查询数据">查询数据
&lt;/h4>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 获取订阅&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sub&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Subscription&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">filter_by&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Tech News&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">first&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 查看所有选择器&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Selectors:&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">selector&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">sub&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">selectors&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;- &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">selector&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">selector&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">selector_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 查看所有变化&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Changes:&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">change&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">sub&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">changes&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;- &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">change&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">old_content&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> -&amp;gt; &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">change&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new_content&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 从变化反向访问选择器&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">first_change&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">sub&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">changes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">first&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Change belongs to selector: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">first_change&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">selector&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>输出示例&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Selectors:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Headline: div.headline
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Summary: p.summary
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Changes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Old Headline -&amp;gt; New Headline
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Change belongs to selector: Headline
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h3 id="总结">总结
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>&lt;code>db.relationship&lt;/code>&lt;/strong> 是 SQLAlchemy 中定义表关系的核心工具。&lt;/li>
&lt;li>它通过外键建立联系，支持对象化导航和级联操作。&lt;/li>
&lt;li>参数如 &lt;code>backref&lt;/code>、&lt;code>lazy&lt;/code> 和 &lt;code>cascade&lt;/code> 提供了灵活的控制方式。&lt;/li>
&lt;li>在实际开发中，合理使用 &lt;code>Relationships&lt;/code> 可以大大简化代码，提高开发效率。&lt;/li>
&lt;/ul></description></item></channel></rss>