- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
9.3 KiB
9.3 KiB
知识点DELETE语句未能删除全部记录的原因分析
问题SQL:
DELETE FROM knowledge_points WHERE material_id = 34;
问题描述:用户反馈该SQL语句未能删除资料34的全部知识点
🔍 问题分析
1. 数据现状
资料34当前有6个知识点记录:
| ID | 知识点名称 | is_deleted | 创建时间 |
|---|---|---|---|
| 1142 | FAB法则介绍 | 0 | 2025-10-17 07:20:28 |
| 1143 | 全面有效的抗衰需求满足 | 0 | 2025-10-17 07:20:28 |
| 1144 | 能量聚焦与精准技术 | 0 | 2025-10-17 07:20:28 |
| 1145 | 多层次治疗与专利炮头 | 0 | 2025-10-17 07:20:28 |
| 1146 | 国内首款医美合规超声刀的安全性与合规性 | 0 | 2025-10-17 07:20:28 |
| 1147 | 提升舒适度与减少副作用 | 0 | 2025-10-17 07:20:28 |
2. 表结构分析
CREATE TABLE `knowledge_points` (
`id` int NOT NULL AUTO_INCREMENT,
`course_id` int DEFAULT NULL COMMENT '所属课程ID',
`material_id` int NOT NULL COMMENT '关联资料ID(必填)',
`name` varchar(200) NOT NULL COMMENT '知识点名称',
`description` text COMMENT '知识点描述',
`is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除', -- ⚠️ 软删除字段
`deleted_at` datetime DEFAULT NULL COMMENT '删除时间',
`created_by` int DEFAULT NULL,
`updated_by` int DEFAULT NULL,
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`type` varchar(50) DEFAULT '理论知识',
`source` tinyint(1) DEFAULT '0',
`topic_relation` varchar(200) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_course_id` (`course_id`),
KEY `idx_is_deleted` (`is_deleted`),
KEY `material_id` (`material_id`),
CONSTRAINT `knowledge_points_ibfk_1` FOREIGN KEY (`course_id`)
REFERENCES `courses` (`id`) ON DELETE CASCADE,
CONSTRAINT `knowledge_points_ibfk_2` FOREIGN KEY (`material_id`)
REFERENCES `course_materials` (`id`) ON DELETE CASCADE
);
关键发现:
- ✅ 表中有
is_deleted字段 → 系统使用软删除机制 - ✅ material_id有外键约束,ON DELETE CASCADE
- ✅ course_id有外键约束,ON DELETE CASCADE
3. 外键约束检查
-- exam_mistakes表引用knowledge_points
CONSTRAINT exam_mistakes_ibfk_4
FOREIGN KEY (knowledge_point_id)
REFERENCES knowledge_points(id)
ON DELETE SET NULL
检查结果:
- ✅ 没有错题记录引用资料34的知识点
- ✅ DELETE_RULE是SET NULL,不会阻止删除
❌ 问题根因
核心问题:系统使用软删除,但SQL使用了物理删除
系统设计:
- 后端代码使用
soft_delete()方法,将is_deleted设置为1 - 前端查询时使用
WHERE is_deleted = 0筛选未删除的记录 - 所有删除操作都是软删除,不会真正从数据库删除记录
用户执行的SQL:
DELETE FROM knowledge_points WHERE material_id = 34; -- ❌ 物理删除
为什么"未能删除全部知识点":
有以下几种可能原因:
原因1:SQL实际上执行成功了(最可能)
- ✅ DELETE语句执行成功,记录被物理删除
- ✅ 但之后系统又重新创建了这些知识点
- 🔍 需要检查是否有后台任务或自动同步机制
原因2:未提交事务
-- 如果在事务中执行但未提交
START TRANSACTION;
DELETE FROM knowledge_points WHERE material_id = 34;
-- 未执行 COMMIT
原因3:没有执行权限
- 用户可能没有DELETE权限
- 需要检查用户权限
原因4:有触发器或其他机制
- 可能有BEFORE DELETE或AFTER DELETE触发器阻止删除
- 需要检查触发器
✅ 正确的删除方法
方法1:使用系统的软删除(推荐)
后端API方式:
# 通过后端API删除(软删除)
curl -X DELETE "https://aiedu.ireborn.com.cn/api/v1/courses/materials/34/knowledge-points/{knowledge_point_id}" \
-H "Authorization: Bearer {token}"
直接SQL方式(软删除):
-- 软删除资料34的所有知识点
UPDATE knowledge_points
SET is_deleted = 1,
deleted_at = NOW()
WHERE material_id = 34
AND is_deleted = 0;
方法2:删除资料(级联删除知识点)
-- 软删除资料,知识点会通过外键级联删除
UPDATE course_materials
SET is_deleted = 1,
deleted_at = NOW()
WHERE id = 34
AND is_deleted = 0;
方法3:物理删除(谨慎使用)
⚠️ 警告:物理删除会永久删除数据,无法恢复!
-- 如果确实需要物理删除
DELETE FROM knowledge_points WHERE material_id = 34;
-- 验证删除结果
SELECT COUNT(*) FROM knowledge_points WHERE material_id = 34;
-- 应该返回 0
🔍 排查步骤
1. 检查记录是否真的还在
-- 查看记录数量
SELECT COUNT(*) as total_count
FROM knowledge_points
WHERE material_id = 34;
-- 查看详细记录
SELECT id, name, is_deleted, deleted_at
FROM knowledge_points
WHERE material_id = 34
ORDER BY id;
2. 检查事务状态
-- 查看当前未提交的事务
SELECT * FROM information_schema.INNODB_TRX;
-- 如果有未提交的事务,提交或回滚
COMMIT;
-- 或
ROLLBACK;
3. 检查触发器
-- 查看knowledge_points表的触发器
SHOW TRIGGERS LIKE 'knowledge_points';
4. 检查删除权限
-- 查看当前用户权限
SHOW GRANTS FOR 'root'@'%';
-- 或查看当前会话权限
SHOW GRANTS FOR CURRENT_USER;
5. 验证外键约束
-- 禁用外键检查(谨慎使用)
SET FOREIGN_KEY_CHECKS = 0;
-- 执行删除
DELETE FROM knowledge_points WHERE material_id = 34;
-- 重新启用外键检查
SET FOREIGN_KEY_CHECKS = 1;
-- 验证结果
SELECT COUNT(*) FROM knowledge_points WHERE material_id = 34;
📝 推荐操作流程
场景A:想要"隐藏"知识点(软删除)✅ 推荐
-- 1. 软删除知识点
UPDATE knowledge_points
SET is_deleted = 1,
deleted_at = NOW(),
updated_by = {user_id} -- 记录操作人
WHERE material_id = 34
AND is_deleted = 0;
-- 2. 验证结果
SELECT COUNT(*) as deleted_count
FROM knowledge_points
WHERE material_id = 34
AND is_deleted = 1;
-- 3. 前端查询时会自动过滤
-- SELECT * FROM knowledge_points WHERE material_id = 34 AND is_deleted = 0;
-- 返回 0 条记录
场景B:需要永久删除记录(物理删除)⚠️ 谨慎
-- 1. 备份数据(重要!)
CREATE TABLE knowledge_points_backup_20251017 AS
SELECT * FROM knowledge_points WHERE material_id = 34;
-- 2. 检查依赖关系
SELECT COUNT(*) as dependent_count
FROM exam_mistakes
WHERE knowledge_point_id IN (
SELECT id FROM knowledge_points WHERE material_id = 34
);
-- 3. 执行物理删除
DELETE FROM knowledge_points WHERE material_id = 34;
-- 4. 验证结果
SELECT COUNT(*) FROM knowledge_points WHERE material_id = 34;
-- 应该返回 0
-- 5. 提交事务
COMMIT;
🎯 解决方案总结
如果要删除资料34的全部知识点:
✅ 推荐:通过后端API删除资料
# 这会软删除资料及其所有知识点
curl -X DELETE "https://aiedu.ireborn.com.cn/api/v1/courses/{course_id}/materials/34" \
-H "Authorization: Bearer {token}"
✅ 次选:SQL软删除
-- 软删除所有知识点
UPDATE knowledge_points
SET is_deleted = 1, deleted_at = NOW()
WHERE material_id = 34 AND is_deleted = 0;
⚠️ 不推荐:SQL物理删除
-- 永久删除(不可恢复)
DELETE FROM knowledge_points WHERE material_id = 34;
COMMIT; -- 记得提交事务!
🔧 故障排除
如果DELETE语句执行后记录仍然存在:
-
检查事务是否提交
COMMIT; -
检查是否在错误的数据库
SELECT DATABASE(); -- 应该返回 kaopeilian -
检查WHERE条件是否正确
SELECT COUNT(*) FROM knowledge_points WHERE material_id = 34; -- 执行DELETE前应该有记录 -
查看MySQL错误日志
docker exec kaopeilian-mysql-dev tail -100 /var/log/mysql/error.log -
测试简单的DELETE
-- 删除单条记录测试 DELETE FROM knowledge_points WHERE id = 1142 LIMIT 1; SELECT * FROM knowledge_points WHERE id = 1142;
📚 相关文档
- 数据库架构:
/root/aiedu/kaopeilian-backend/数据库架构-统一版.md - Service层代码:
/root/aiedu/kaopeilian-backend/app/services/course_service.py - Base Service:
/root/aiedu/kaopeilian-backend/app/services/base_service.py
💡 经验教训
-
理解系统的删除机制
- 软删除系统中,直接使用DELETE可能导致数据不一致
- 应该使用系统提供的API或软删除SQL
-
谨慎使用物理删除
- 物理删除是不可逆的
- 删除前应该备份数据
- 检查是否有外键依赖
-
使用事务
- 重要的删除操作应该在事务中进行
- 先验证后提交
-
记录操作日志
- 删除操作应该记录操作人和操作时间
- 便于追溯和审计
结论:用户的DELETE语句从语法上是正确的,能够执行。如果执行后记录仍然存在,最可能的原因是:
- 事务未提交
- 后台任务重新创建了记录
- 在错误的数据库环境执行
建议使用软删除而不是物理删除,以保持系统一致性。