【已解决】SqlAlchemy 插入 MySQL JSON 字段时 None 变为 ‘null‘ 字符串,WHERE IS NULL 失效

在使用 SqlAlchemy 操作 MySQL 数据库的过程中,当向 JSON 类型的字段插入None值时,数据库中最终存储的不是 MySQL 原生的NULL,而是字符串'null'。这个问题会导致后续查询(比如WHERE IS NULL)完全失效,给数据处理带来诸多麻烦。本文将详细分析问题原因,并给出简洁有效的解决方案。

一、问题复现:None 变成了 'null' 字符串

先通过一段简单的代码复现这个问题,让你直观看到现象:

python 复制代码
import asyncio

from sqlalchemy import Column, Integer, String, JSON, select
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase

DB_URL = "mysql+asyncmy://root:root@localhost:3306/test_db?charset=utf8mb4"
async_engine = create_async_engine(DB_URL, echo=True)
AsyncSession = async_sessionmaker(bind=async_engine, expire_on_commit=False)

class Base(DeclarativeBase):
    __abstract__ = True

# 定义模型
class TestJsonTable(Base):
    __tablename__ = "test_json_table"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50))
    json_data = Column(JSON)

async def start():
    # 创建数据表
    async with async_engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)

    # 插入测试数据
    async with AsyncSession() as session:
        async with session.begin():
            test_data = TestJsonTable(name="测试数据", json_data=None)
            session.add(test_data)

        result = await session.execute(select(TestJsonTable).where(TestJsonTable.json_data== 'null'))
        print(*(e.json_data for e in result.scalars().all()), sep='\n')

if __name__ == '__main__':
    asyncio.run(start())

运行代码后,可以发现在控制台输出的 SQL 执行日志中,插入测试数据 SQL 的字段会填充为'null'而非None

数据库中json_data字段存储的也是字符串'null',而非 MySQL 的NULL

执行SELECT * FROM test_json_table WHERE json_data IS NULL会返回空结果,因为字段值是字符串而非真正的 NULL。

但是在代码中查询返回的result.json_dataNone

二、问题原因:none_as_null 参数默认值为 False

首先要明确两个容易混淆的概念,这是解决问题的关键:

类型 数据库存储形态 SqlAlchemy 查询结果 WHERE 字段 IS NULL 是否生效
MySQL 原生 NULL 空(无值) None ✅ 生效
JSON 格式的 null JSON 格式的 null None ❌ 不生效

数据库中 JSON 字段实际存储的是字符串'null'(JSON 格式的 null),但通过 SqlAlchemy 查询后打印该字段却显示为None,这是因为 SqlAlchemy 对 JSON 类型的解析逻辑导致的「存储形态」和「查询结果」不一致。

SqlAlchemy 在读取时会按照 JSON 规范,把 JSON 的null解析为 Python 的None,所以打印出来是None,但数据库里实际存储的并不是 MySQL 原生的 NULL。

SqlAlchemy 的JSONJSONB列类型(MySQL 中JSONBJSON的别名)有一个关键参数none_as_null

  • 默认值False,此时会将 Python 的None转换为字符串'null'插入数据库;
  • 设置为 True :会将 Python 的None转换为 MySQL 原生的NULL值插入。

这个参数的设计是为了兼容不同数据库的 JSON 类型处理逻辑,但 MySQL 场景下默认值会导致不符合预期的结果。

三、解决方案:初始化 JSON 字段时设置 none_as_null=True

在定义 sqlalchemy 模型 JSON 列时,显式设置none_as_null=True即可解决问题,修改后的核心代码如下:

python 复制代码
class TestJsonTable(Base):
    __tablename__ = "test_json_table"
    
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50))
    # 关键修改:设置none_as_null=True
    json_data = Column(JSON(none_as_null=True))

JSON(none_as_null=True) 是关键,它会让 SqlAlchemy 将 Python 的None转换为 MySQL 原生 NULL 插入,而非 JSON 的'null'字符串。

再次执行代码,

数据库中json_data字段存储的是 MySQL 原生的NULL

执行SELECT * FROM test_json_table WHERE json_data IS NULL能正确返回数据:

代码中查询返回的result.json_dataNone,类型为NoneType


这个问题虽然隐蔽,但解决方法非常简单,只需要在定义列时多添加一个参数即可。希望本文能帮助你避开这个坑,让 SqlAlchemy 的 JSON 字段操作更符合预期。

相关推荐
ℳ₯㎕ddzོꦿ࿐1 小时前
[特殊字符] 【踩坑记录】没调 startPage(),SQL 却被自动分页了?
数据库·sql
之歆1 小时前
MySQL 数据库理论、安装、查询、事务与备份恢复
数据库·mysql·adb
SQL必知必会1 小时前
SQL 描述性统计:超越平均值和计数
数据库·sql
ActionTech1 小时前
数据集推荐 06 | 首款 NL2GeoSQL 的测试基准和数据集来了!
数据库·人工智能·sql
郝学胜-神的一滴2 小时前
Python中的Dict子类:优雅扩展字典的无限可能
开发语言·python
长谷深风1112 小时前
Redis入门:从MySQL到高效缓存的飞跃
redis·后端·mysql·缓存·nosql·java 开发
码云数智-大飞2 小时前
跳出索引思维定式:一次基于业务逻辑的非典型 SQL 优化实践
数据库·sql
二十雨辰2 小时前
[python]-面向对象高级
python
PD我是你的真爱粉2 小时前
Redis基础与数据结构
数据结构·数据库·redis