首页
学习
活动
专区
圈层
工具
发布

新来个技术总监:发现谁再用 delete 删数据直接开除!

那天早上我们例会,本来以为又是那种平平无奇的周会,结果新来的技术总监一开口就炸场了:“以后谁在生产库用 delete 删数据,直接开除。”全场安静三秒钟,然后一阵低声嘀咕。 其实我第一反应是笑的,这话听起来太夸张了。但他后面举的例子,真让人不敢笑。

线上删库的噩梦

总监说,他以前在一家互联网公司干的时候,有个研发在凌晨修复个脏数据,用了句简单的:

DELETE FROM user_order;

原本想删几条测试订单,结果忘了加 where 条件。数据库没做备份,binlog 又关着,所有订单全没了。那公司当天赔了三百万。 从那以后,他就发了死命令——所有删除操作必须逻辑删除,严禁直接 delete。

什么是逻辑删除?

其实逻辑删除很简单,就是不真正删掉数据,而是给表加个标记字段,比如:

@Column(name = "deleted")

private Boolean deleted = false;

删数据的时候,我们不删,而是把 deleted 改成 true:

@Modifying

@Query("UPDATE User u SET u.deleted = true WHERE u.id = :id")

void softDelete(@Param("id") Long id);

查询时自动带上过滤条件:

@Query("SELECT u FROM User u WHERE u.deleted = false")

List<User> findAllActiveUsers();

这样查出来的就是没被“删”的数据。 逻辑删除的好处是:误删可以恢复、日志可追踪、历史数据还在。缺点就是——表越来越大。

表越来越大怎么办?

我以前也烦这个问题。逻辑删除后几百万条数据,查询会慢得要命。解决办法其实挺多:

加索引给deleted字段加上索引,这样查询活跃数据时过滤更快。

CREATE INDEX idx_deleted ON user(deleted);

分表归档定期把deleted = true的数据转移到历史表,比如:

INSERT INTO user_history SELECT * FROM user WHERE deleted = true;

DELETE FROM user WHERE deleted = true;

视图隔离也可以直接建个视图给业务层用,只暴露活跃数据:

CREATE VIEW active_user AS

SELECT * FROM user WHERE deleted = false;

ORM 层自动化

有时候手写条件太麻烦,Spring JPA 或 MyBatis 其实都能自动处理。 比如用 MyBatis Plus,就一个注解搞定:

@TableLogic(value = "0", delval = "1")

private Integer deleted;

写 delete 语句时,它自动转成 update。 你写:

userMapper.deleteById(1001);

它执行的其实是:

UPDATE user SET deleted = 1 WHERE id = 1001;

不信你可以开个 SQL 日志看看。

为什么 delete 这么危险?

有个很现实的原因:人会犯错。 尤其是凌晨部署、线上热修、写错 where 条件的时候。 比如这条:

DELETE FROM orders WHERE user_id IN (SELECT id FROM users WHERE status='test');

结果子查询多了个括号,删了全表。 或者你用 JPA 写个:

userRepository.deleteAll();

结果开发环境没切回来,一键清空生产库。 这些事不止发生过一次——我自己都差点干过。

实际开发中怎么防?

公司现在的规定基本是这样:

数据库层加保护

delete 权限只给 DBA;

必须开 binlog;

表必须有逻辑删除字段;

所有 delete 操作写触发器自动备份:

CREATE TRIGGER before_user_delete

BEFORE DELETE ON user

FOR EACH ROW

INSERT INTO user_backup SELECT * FROM user WHERE id = OLD.id;

代码层做限制自研的数据访问层直接禁止 delete:

if (sql.contains("delete")) {

  throw new RuntimeException("禁止执行物理删除操作!");

}

定期清理真正需要清理的老数据,交给专门的任务处理:

@Scheduled(cron = "0 0 2 * * ?")

public void cleanArchivedData() {

  jdbcTemplate.update("DELETE FROM user_history WHERE create_time < DATE_SUB(NOW(), INTERVAL 180 DAY)");

}

这样既安全,又能保持数据库干净。

写在最后

总监那天说完“delete 开除”的话,全场没人反驳。 因为大家都知道,删错一次数据,真的能让人失业。 逻辑删除看起来啰嗦,但它救命。

后来我在 review 别人代码时看到delete from,下意识就出冷汗。 真别觉得夸张,这种事一旦发生,不是写个 SQL 能补回来的。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OirdOsa7huxzGEPH9JiZmyCw0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。
领券