executedelete在分布式数据库中不可用,因其生成的单节点sql无法被协调节点自动分发至数据节点,需显式使用含分片键的分布式delete语句,并配合异步清理与事务补偿机制。

为什么 ExecuteDelete 在分布式数据库里不能直接用
EF Core 的 ExecuteDelete 生成的是单节点 DELETE SQL,而分布式数据库(如 YashanDB)的协调节点(CN)不会自动把这类语句拆分到各数据节点(DN)执行。你调用它,大概率只会删掉 CN 所在节点的元数据或报错——实际数据还在各 DN 上纹丝不动。
常见错误现象:ExecuteDelete 返回 0 行影响,日志里却没报错;或者只删了部分分片,监控显示某些 DN 的磁盘空间毫无变化。
- 根本原因:
ExecuteDelete依赖 ORM 对底层 SQL 引擎的深度适配,目前主流分布式数据库的 JDBC/ODBC 驱动不识别该操作语义 - YashanDB 等系统要求跨节点 DML 必须显式走分布式执行计划,不能靠客户端拼 SQL
- 即使你在应用层手动拼出
DELETE FROM t WHERE shard_key IN (...),也得确保shard_key覆盖全部目标分片,否则漏删
跨节点 DELETE 的正确打开方式:用分布式 DELETE 语句 + 分片键过滤
YashanDB 支持标准 SQL 语法的跨节点 DELETE,但前提是 WHERE 条件中必须包含分片键(shard key),否则 CN 无法路由,会拒绝执行或退化为广播删除(极慢且危险)。
示例(假设 logs 表按 tenant_id 哈希分片):
DELETE FROM logs WHERE tenant_id = 123 AND created_at <p>这个语句会被 CN 解析后,精准下发到持有 <code>tenant_id=123</code> 数据的那个 DN 上执行,不跨节点、不拉数据、不锁全集群。</p>
- 必须用等值条件匹配分片键,范围查询(如
tenant_id > 100)会导致广播,慎用 - 多个分片键值要拆成多条语句并发执行,比如删
tenant_id IN (123, 456, 789),应发三次独立 DELETE - 避免在 WHERE 中混用非分片字段做复杂逻辑,如
tenant_id = ? AND JSON_CONTAINS(meta, '"flag":true'),可能触发本地计算+全量扫描
大规模跨节点清理必须配合异步任务与状态标记
直接 DELETE 百万级数据会阻塞 CN、拖慢其他查询,还可能触发 DN 的长事务保护机制自动中断。生产环境必须走“标记 + 异步清理”双阶段流程。
推荐做法是复用软删除模式,但把 deleted_at 字段作为分片键的一部分(或建在分片键索引上),让后续清理能继续走分片路由:
UPDATE logs SET deleted_at = NOW() WHERE tenant_id = 123 AND created_at <p>再由后台定时任务按 <code>tenant_id</code> 分批执行物理删除:</p> <pre class="brush:php;toolbar:false;">DELETE FROM logs WHERE tenant_id = 123 AND deleted_at IS NOT NULL LIMIT 10000;
- 每次
LIMIT控制在 1w 行以内,防止单次执行太久 - 任务需记录已处理的
tenant_id和最大id(如果有自增主键),避免重复或遗漏 - YashanDB 的共享集群模式下,
deleted_at IS NOT NULL查询若没走分片键,仍可能广播——务必确认执行计划里有 “Shard Route” 步骤
容易被忽略的权限与事务边界问题
分布式 DELETE 不是原子性黑盒。CN 提交后,某个 DN 执行失败,整个语句就回滚;但如果用了异步清理,事务就断开了。
真实风险点:
- CN 默认开启两阶段提交(2PC),但超时时间默认较短(YashanDB 是 60 秒),大删除容易触发
distributed transaction timeout - 应用连接池若配置了 auto-commit=true,每次 DELETE 都是独立事务,无法和前面的 UPDATE 组成业务事务
- 跨节点 DELETE 无法加应用层锁(如 Redis 分布式锁对数据库无效),并发清理同一
tenant_id可能冲突
最稳妥的做法:清理任务用专用数据库账号,该账号只允许对 logs 表执行 DELETE,并关闭 2PC 改用最终一致性补偿——比如先写清理记录表,再删数据,失败时靠对账脚本兜底。











