feat: 初始化考培练系统项目
- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
378
知识库/知识点删除问题分析.md
Normal file
378
知识库/知识点删除问题分析.md
Normal file
@@ -0,0 +1,378 @@
|
||||
# 知识点DELETE语句未能删除全部记录的原因分析
|
||||
|
||||
**问题SQL**:
|
||||
```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. 表结构分析
|
||||
|
||||
```sql
|
||||
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. 外键约束检查
|
||||
|
||||
```sql
|
||||
-- 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**:
|
||||
```sql
|
||||
DELETE FROM knowledge_points WHERE material_id = 34; -- ❌ 物理删除
|
||||
```
|
||||
|
||||
**为什么"未能删除全部知识点"**:
|
||||
|
||||
有以下几种可能原因:
|
||||
|
||||
#### 原因1:SQL实际上执行成功了(最可能)
|
||||
- ✅ DELETE语句执行成功,记录被物理删除
|
||||
- ✅ 但之后系统又重新创建了这些知识点
|
||||
- 🔍 需要检查是否有后台任务或自动同步机制
|
||||
|
||||
#### 原因2:未提交事务
|
||||
```sql
|
||||
-- 如果在事务中执行但未提交
|
||||
START TRANSACTION;
|
||||
DELETE FROM knowledge_points WHERE material_id = 34;
|
||||
-- 未执行 COMMIT
|
||||
```
|
||||
|
||||
#### 原因3:没有执行权限
|
||||
- 用户可能没有DELETE权限
|
||||
- 需要检查用户权限
|
||||
|
||||
#### 原因4:有触发器或其他机制
|
||||
- 可能有BEFORE DELETE或AFTER DELETE触发器阻止删除
|
||||
- 需要检查触发器
|
||||
|
||||
---
|
||||
|
||||
## ✅ 正确的删除方法
|
||||
|
||||
### 方法1:使用系统的软删除(推荐)
|
||||
|
||||
**后端API方式**:
|
||||
```bash
|
||||
# 通过后端API删除(软删除)
|
||||
curl -X DELETE "https://aiedu.ireborn.com.cn/api/v1/courses/materials/34/knowledge-points/{knowledge_point_id}" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
```
|
||||
|
||||
**直接SQL方式(软删除)**:
|
||||
```sql
|
||||
-- 软删除资料34的所有知识点
|
||||
UPDATE knowledge_points
|
||||
SET is_deleted = 1,
|
||||
deleted_at = NOW()
|
||||
WHERE material_id = 34
|
||||
AND is_deleted = 0;
|
||||
```
|
||||
|
||||
### 方法2:删除资料(级联删除知识点)
|
||||
|
||||
```sql
|
||||
-- 软删除资料,知识点会通过外键级联删除
|
||||
UPDATE course_materials
|
||||
SET is_deleted = 1,
|
||||
deleted_at = NOW()
|
||||
WHERE id = 34
|
||||
AND is_deleted = 0;
|
||||
```
|
||||
|
||||
### 方法3:物理删除(谨慎使用)
|
||||
|
||||
**⚠️ 警告**:物理删除会永久删除数据,无法恢复!
|
||||
|
||||
```sql
|
||||
-- 如果确实需要物理删除
|
||||
DELETE FROM knowledge_points WHERE material_id = 34;
|
||||
|
||||
-- 验证删除结果
|
||||
SELECT COUNT(*) FROM knowledge_points WHERE material_id = 34;
|
||||
-- 应该返回 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 排查步骤
|
||||
|
||||
### 1. 检查记录是否真的还在
|
||||
|
||||
```sql
|
||||
-- 查看记录数量
|
||||
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. 检查事务状态
|
||||
|
||||
```sql
|
||||
-- 查看当前未提交的事务
|
||||
SELECT * FROM information_schema.INNODB_TRX;
|
||||
|
||||
-- 如果有未提交的事务,提交或回滚
|
||||
COMMIT;
|
||||
-- 或
|
||||
ROLLBACK;
|
||||
```
|
||||
|
||||
### 3. 检查触发器
|
||||
|
||||
```sql
|
||||
-- 查看knowledge_points表的触发器
|
||||
SHOW TRIGGERS LIKE 'knowledge_points';
|
||||
```
|
||||
|
||||
### 4. 检查删除权限
|
||||
|
||||
```sql
|
||||
-- 查看当前用户权限
|
||||
SHOW GRANTS FOR 'root'@'%';
|
||||
|
||||
-- 或查看当前会话权限
|
||||
SHOW GRANTS FOR CURRENT_USER;
|
||||
```
|
||||
|
||||
### 5. 验证外键约束
|
||||
|
||||
```sql
|
||||
-- 禁用外键检查(谨慎使用)
|
||||
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:想要"隐藏"知识点(软删除)✅ 推荐
|
||||
|
||||
```sql
|
||||
-- 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:需要永久删除记录(物理删除)⚠️ 谨慎
|
||||
|
||||
```sql
|
||||
-- 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删除资料**
|
||||
```bash
|
||||
# 这会软删除资料及其所有知识点
|
||||
curl -X DELETE "https://aiedu.ireborn.com.cn/api/v1/courses/{course_id}/materials/34" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
```
|
||||
|
||||
**✅ 次选:SQL软删除**
|
||||
```sql
|
||||
-- 软删除所有知识点
|
||||
UPDATE knowledge_points
|
||||
SET is_deleted = 1, deleted_at = NOW()
|
||||
WHERE material_id = 34 AND is_deleted = 0;
|
||||
```
|
||||
|
||||
**⚠️ 不推荐:SQL物理删除**
|
||||
```sql
|
||||
-- 永久删除(不可恢复)
|
||||
DELETE FROM knowledge_points WHERE material_id = 34;
|
||||
COMMIT; -- 记得提交事务!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 故障排除
|
||||
|
||||
### 如果DELETE语句执行后记录仍然存在:
|
||||
|
||||
1. **检查事务是否提交**
|
||||
```sql
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
2. **检查是否在错误的数据库**
|
||||
```sql
|
||||
SELECT DATABASE();
|
||||
-- 应该返回 kaopeilian
|
||||
```
|
||||
|
||||
3. **检查WHERE条件是否正确**
|
||||
```sql
|
||||
SELECT COUNT(*) FROM knowledge_points WHERE material_id = 34;
|
||||
-- 执行DELETE前应该有记录
|
||||
```
|
||||
|
||||
4. **查看MySQL错误日志**
|
||||
```bash
|
||||
docker exec kaopeilian-mysql-dev tail -100 /var/log/mysql/error.log
|
||||
```
|
||||
|
||||
5. **测试简单的DELETE**
|
||||
```sql
|
||||
-- 删除单条记录测试
|
||||
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`
|
||||
|
||||
---
|
||||
|
||||
## 💡 经验教训
|
||||
|
||||
1. **理解系统的删除机制**
|
||||
- 软删除系统中,直接使用DELETE可能导致数据不一致
|
||||
- 应该使用系统提供的API或软删除SQL
|
||||
|
||||
2. **谨慎使用物理删除**
|
||||
- 物理删除是不可逆的
|
||||
- 删除前应该备份数据
|
||||
- 检查是否有外键依赖
|
||||
|
||||
3. **使用事务**
|
||||
- 重要的删除操作应该在事务中进行
|
||||
- 先验证后提交
|
||||
|
||||
4. **记录操作日志**
|
||||
- 删除操作应该记录操作人和操作时间
|
||||
- 便于追溯和审计
|
||||
|
||||
---
|
||||
|
||||
**结论**:用户的DELETE语句从语法上是正确的,能够执行。如果执行后记录仍然存在,最可能的原因是:
|
||||
1. 事务未提交
|
||||
2. 后台任务重新创建了记录
|
||||
3. 在错误的数据库环境执行
|
||||
|
||||
建议使用软删除而不是物理删除,以保持系统一致性。
|
||||
|
||||
Reference in New Issue
Block a user