1. 逻辑删除的概念与价值
在数据库设计中,逻辑删除(Soft Delete)是一种常见的数据管理策略。与物理删除(直接从数据库中移除记录)不同,逻辑删除通过标记记录状态来实现"伪删除",保留数据的同时使其在常规查询中不可见。
Kite框架实现逻辑删除的核心价值在于:
- 数据安全:避免误操作导致数据永久丢失
- 审计追踪:保留完整的历史记录用于合规审查
- 业务连续性:支持数据恢复和业务回滚
- 关联数据保护:防止级联删除破坏数据完整性
2. Kite框架下的实现方案
2.1 基础字段设计
典型的逻辑删除实现需要在数据表中添加状态字段:
sql复制ALTER TABLE users ADD COLUMN is_deleted TINYINT(1) DEFAULT 0;
ALTER TABLE users ADD COLUMN delete_time DATETIME NULL;
字段说明:
is_deleted:删除标记(0-未删除,1-已删除)delete_time:删除时间戳(用于审计)
2.2 实体类注解配置
在Kite框架中,通过@Entity注解配置逻辑删除字段:
java复制@Entity
@Table(name = "users")
public class User {
@Id
private Long id;
@Column(name = "is_deleted")
@LogicDelete
private Integer isDeleted;
@Column(name = "delete_time")
private LocalDateTime deleteTime;
// 其他字段和getter/setter
}
@LogicDelete注解是关键,它告诉Kite框架该字段用于逻辑删除标记。
2.3 全局拦截器配置
Kite通过SQL拦截器自动改写CRUD操作:
java复制@Configuration
public class KiteConfig {
@Bean
public LogicDeleteInterceptor logicDeleteInterceptor() {
return new LogicDeleteInterceptor()
.setDeleteValue("1") // 删除状态值
.setNormalValue("0"); // 正常状态值
}
}
拦截器会:
- 在SELECT查询自动添加
WHERE is_deleted = 0 - 在DELETE操作转为UPDATE设置
is_deleted = 1 - 在UPDATE操作自动排除已删除记录
3. 高级应用场景
3.1 多租户隔离增强
在SaaS系统中结合租户ID实现安全隔离:
sql复制SELECT * FROM orders
WHERE tenant_id = 123
AND is_deleted = 0;
3.2 历史数据归档策略
java复制@Scheduled(cron = "0 0 3 * * ?")
public void archiveDeletedData() {
// 将30天前的删除数据迁移到历史表
kiteTemplate.execute(
"INSERT INTO users_history " +
"SELECT * FROM users " +
"WHERE is_deleted = 1 " +
"AND delete_time < DATE_SUB(NOW(), INTERVAL 30 DAY)");
// 清理原表
kiteTemplate.execute(
"DELETE FROM users " +
"WHERE is_deleted = 1 " +
"AND delete_time < DATE_SUB(NOW(), INTERVAL 30 DAY)");
}
3.3 审计日志集成
通过Kite的事件监听机制记录删除操作:
java复制@EntityListener
public class DeleteAuditListener {
@PreRemove
public void preRemove(Object entity) {
AuditLog log = new AuditLog();
log.setOperation("DELETE");
log.setEntityType(entity.getClass().getSimpleName());
log.setOperator(CurrentUser.get());
log.setOperationTime(LocalDateTime.now());
kiteTemplate.save(log);
}
}
4. 性能优化实践
4.1 索引设计建议
sql复制-- 复合索引优化查询性能
CREATE INDEX idx_status_tenant ON users (is_deleted, tenant_id);
-- 单独索引优化归档任务
CREATE INDEX idx_delete_time ON orders (delete_time);
4.2 查询优化技巧
避免全表扫描的查询方式:
java复制// 反例:会导致全表扫描
kiteTemplate.queryForList(
"SELECT * FROM products WHERE name LIKE ?",
"%" + keyword + "%");
// 正例:使用覆盖索引
kiteTemplate.queryForList(
"SELECT id,name FROM products " +
"WHERE is_deleted = 0 AND name LIKE ? " +
"ORDER BY create_time DESC LIMIT 100",
keyword + "%");
4.3 批量处理方案
java复制// 分批处理避免大事务
int batchSize = 500;
List<Long> ids = getExpiredIds();
for (List<Long> batch : Lists.partition(ids, batchSize)) {
kiteTemplate.execute(
"UPDATE orders SET is_deleted = 1, " +
"delete_time = NOW() " +
"WHERE id IN (:ids)",
Collections.singletonMap("ids", batch));
}
5. 常见问题解决方案
5.1 唯一约束冲突
问题场景:已删除的用户名需要允许重新注册
解决方案:
sql复制-- 原始唯一约束
ALTER TABLE users ADD UNIQUE (username);
-- 修改为条件唯一约束
ALTER TABLE users ADD CONSTRAINT uk_username
UNIQUE (username, is_deleted);
5.2 关联查询处理
使用Kite的关联查询注解:
java复制@Entity
public class Order {
@ManyToOne
@JoinColumn(name = "user_id")
@Where(clause = "is_deleted = 0") // 关键注解
private User user;
}
5.3 统计报表准确性
java复制// 统计时需要显式包含已删除数据
public SalesStats getSalesStats(LocalDate date) {
return kiteTemplate.queryForObject(
"SELECT COUNT(*) as totalOrders, " +
"SUM(CASE WHEN is_deleted = 0 THEN amount ELSE 0 END) as validAmount " +
"FROM orders " +
"WHERE create_date = ?",
new BeanPropertyRowMapper<>(SalesStats.class),
date);
}
6. 最佳实践建议
-
字段命名标准化:建议使用
is_deleted而非deleted_flag等变体,保持团队统一 -
删除时间记录:即使业务暂时不需要也建议保留,后续审计时价值巨大
-
定期归档策略:根据数据量制定合理的归档周期(如30天/90天)
-
权限隔离:限制只有特定角色可以执行物理删除操作
-
前端适配:列表页增加"显示已删除"筛选选项,详情页展示删除状态标记
-
文档注释:在数据库设计文档中明确标注逻辑删除字段及其含义
sql复制COMMENT ON COLUMN users.is_deleted IS '逻辑删除标记(0-正常 1-删除)';
在微服务架构中,建议通过统一配置中心管理逻辑删除的字段名和状态值,确保各服务保持一致。Kite框架支持动态读取配置:
java复制@Value("${logic.delete.field:is_deleted}")
private String logicDeleteField;
@Bean
public LogicDeleteInterceptor logicDeleteInterceptor() {
return new LogicDeleteInterceptor()
.setDeleteField(logicDeleteField);
}
