is_deleted字段应使用tinyint unsigned not null default 0:unsigned防止负数写入,not null避免null导致查询漏行,default 0使新数据默认可见;禁用bit(1)、boolean和varchar,因兼容性差或易引发类型匹配错误。

is_deleted 字段该用什么类型和默认值
直接用 TINYINT UNSIGNED NOT NULL DEFAULT 0,别碰 BIT(1)、BOOLEAN 或 VARCHAR。MySQL 没原生布尔类型,BOOLEAN 只是 TINYINT(1) 别名,ORM 或 JDBC 驱动常把它当数值参与计算;BIT(1) 在某些客户端里会转成字节或布尔对象,导致 WHERE is_deleted = 1 查不到数据——实际存的是二进制 b'1',跟十进制 1 类型不匹配。
UNSIGNED 防负数写入(比如误设成 -1);NOT NULL 避免 NULL 行被 WHERE is_deleted = 0 漏掉(NULL = 0 永远为 false);DEFAULT 0 让新插入数据天然“可见”,不用每条 INSERT 都手动写 is_deleted = 0。
所有 SELECT 必须显式加 AND is_deleted = 0
不能靠 MyBatis-Plus 的 @TableLogic 全局拦截,手写 SQL、DBA 直连、定时任务脚本、报表工具直查、跨库 JOIN……这些场景全绕过 ORM。漏写就暴露已删数据,轻则订单错乱,重则用户隐私泄露。
多表 JOIN 时注意位置:LEFT JOIN order o ON u.id = o.user_id AND o.is_deleted = 0 —— 条件必须写在 ON 里,不是 WHERE;否则 LEFT JOIN 会退化成 INNER JOIN。
子查询、EXISTS、IN 里也得带 AND is_deleted = 0,否则可能关联到已删记录,返回错误结果。
唯一索引冲突怎么破:改索引,不是改业务逻辑
比如 username 有唯一索引,用户 A 被逻辑删除后,新用户 B 注册同名账号失败。这不是“不该唯一”,而是索引没区分状态。
执行:ALTER TABLE user DROP INDEX idx_username, ADD UNIQUE INDEX idx_username_deleted (username, is_deleted);
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
这样 (username='alice', is_deleted=0) 和 (username='alice', is_deleted=1) 就是两个独立索引项。注意:如果业务要求“一个用户名只能存在一个未删记录”,这个联合唯一索引刚好满足;但若允许同名多个已删记录(比如不同时间删的),也没问题——is_deleted 是 TINYINT,不是 ENUM 或 BIT,后续扩展更稳。
UPDATE / DELETE 操作要防重复与误覆盖
逻辑删除本质是 UPDATE,不是 DELETE。直接写 UPDATE user SET is_deleted = 1 WHERE id = 123 风险很大:
- 若该行已被删过,再执行一次等于无意义更新(浪费)
- 恢复操作(
SET is_deleted = 0)可能被并发请求覆盖 - 执行后
ROW_COUNT()返回 0,你得立刻告诉前端“记录不存在或已删”,而不是静默成功
安全写法:
软删:UPDATE user SET is_deleted = 1 WHERE id = 123 AND is_deleted = 0
恢复:UPDATE user SET is_deleted = 0 WHERE id = 123 AND is_deleted = 1
执行完立刻检查影响行数,等于 0 就报错或提示,别让它悄无声息地吞掉问题。










