一个问题,就难住一群3年+的Java后端[特殊字符]

今天面了三个3年+的Java后端,

问了一个这样的问题:

“数据库逻辑删除后,怎么做唯一性约束?”

结果都答得磕磕巴巴,只能让他们回家等通知了,建议多刷多练一下场景题再来面试吧!

如果是你,会怎么回答这个问题呢?

由于篇幅限制,只能给大家展示小部分内容,

全套面试笔记及答案【点击此处】即可免费获取

1.22 数据库逻辑删除后,怎么做唯一性约束?

1.22.1 面试考察重点

1. 面试官的考察目标

  • 业务设计能力:对逻辑删除业务场景的理解
  • 数据库技能:对约束机制的深入掌握
  • 解决方案:复杂场景下的问题解决能力
  • 工程思维:实际落地时的综合考量

2. 关键关注点

  • 约束范围:区分已删除和未删除数据
  • 性能影响:方案对数据库性能的影响
  • 查询复杂度:对业务查询的侵入性
  • 数据恢复:逻辑删除恢复时的处理

1.22.2 面试核心知识点详解

不知道大家有没有遇到这么一种业务场景,在业务中有个唯一约束A,当该业务进行逻辑删除后(设置标

记为删除状态),再往唯一约束列插入相同的值时,此时会报Duplicate entry,但在业务上,该值是必

须要插入的。今天我们就来聊聊处理这种业务场景的几种思路

方案一:不采用逻辑删除,直接物理删除

方案二:新建历史表

主表进行物理删除,同时将删除的记录保存到历史表中

方案三:取消表的唯一约束,同时引入redis来保证唯一约束

取消表的唯一约束,在项目中引入redis,通过redis来判重,新增时往redis set记录,删除时,删除

redis记录

方案四:变更删除标记为时间戳

将删除状态不以0,1表示,而是以时间戳为值,然后将删除状态为与之前的唯一约束A重新组成唯一联合

约束index(A、del_flag),删除时变更del_flag的时间戳

方案五:保留删除标记,同时新建一个字段del_unique_key

保留删除状态位,再新增一个字段del_unique_key,该字段默认值为0,字段类型和大小与主键id保持一

致,同时与原先的唯一约束重新组成联合唯一约束index(A,del_unique_key),业务进行逻辑删除,变更

del_unique_key的值为该删除行的主键id

方案的取舍

方案一得从业务的角度上考虑了,如果物理删除,对业务无损,那就无所谓了。方案二等于需要删除的记

录的表都需要有历史表,如果仅仅是用来实现记录删除记录,感觉有点大材小用。方案三引入redis,虽然

也可以解决问题,但是又额外增加复杂度,同时还得保证redis和数据库的一致性。方案四和方案五其实实

现的思路是一样,不过如果已经是在线上跑的业务,还是推荐用第五种方案,毕竟新增字段正常对已有的

业务影响相对较小,如果是第四种方案,直接将标志位修改为时间戳,可能还会涉及改业务。如果是新增

业务,第四种和第五种方案比较推荐

1.223 面试前准备建议和思路

1. 掌握逻辑删除实现方式

  • 标志位设计(is_deleted)
  • 状态字段设计(status=active/deleted)
  • 删除时间戳(deleted_at)

2. 研究唯一约束技术

  • 条件唯一索引
  • 复合索引
  • 触发器维护
  • 应用层校验

3. 准备不同数据库方案

数据库

支持特性

MySQL

函数索引(8.0+)

Oracle

函数索引

SQL Server

过滤索引

PostgreSQL

部分索引

1.22.4 📌面试必过的回答思路

1. 标准回答结构

  • 问题分析:逻辑删除对唯一约束的破坏
  • 解决方案:分级提供多种技术方案
  • 实现细节:具体数据库的实现示例
  • 优劣对比:各方案的权衡选择
  • 最佳实践:生产环境推荐方案

2. 完整回答示例

"针对逻辑删除后的唯一性约束问题,我将提供以下系统化解决方案:

1. 问题本质分析

逻辑删除后,传统唯一索引依然会认为被标记删除的数据存在,导致新插入的"相同"数据违反约束:

-- 原始唯一索引
ALTER TABLE users ADD UNIQUE (username);

-- 逻辑删除后出现问题
UPDATE users SET is_deleted = 1 WHERE id = 1;  -- 删除"admin"
INSERT INTO users (username) VALUES ('admin'); -- 被阻止但业务需要允许

2. 解决方案集

方案一:复合唯一索引(兼容性最好)

-- MySQL/PostgreSQL等通用方案
ALTER TABLE users 
ADD UNIQUE INDEX uk_username_active (username, is_deleted);

-- 特殊处理:已删除记录is_deleted=id
UPDATE users SET is_deleted = id WHERE id = 1;

优点:全数据库兼容

缺点:需特殊处理删除标记

方案二:过滤索引(现代数据库支持)

-- SQL Server/PG/MySQL8.0+
CREATE UNIQUE INDEX uk_username_active 
ON users(username) 
WHERE is_deleted = 0;

优点:直观清晰

缺点:数据库版本要求

方案三:删除标记+默认值组合

-- 使用NULL避开唯一约束
ALTER TABLE users 
ADD UNIQUE INDEX uk_username (username, deleted_at);

-- 删除时
UPDATE users SET deleted_at = NOW() WHERE id = 1;

优点:利用NULL不参与约束特性

缺点:需改造删除逻辑

 

方案四:应用层+数据库双校验

// 事务中先查询后插入
@Transactional
public void createUser(String username) {
    if (userRepo.existsActiveByName(username)) {
        throw new ConflictException("用户名已存在");
    }
    userRepo.save(new User(username));
}

优点:灵活可控

缺点:存在并发漏洞

3. 生产级推荐方案

分阶段实现

  1. 基础防护:复合索引
ALTER TABLE products 
ADD UNIQUE INDEX uk_product_code (code, is_deleted);
  1. 增强防护:应用层校验
@Service
public class ProductService {
    @Transactional
    public Product createProduct(String code) {
        if (productRepository.existsByCodeAndNotDeleted(code)) {
            throw new BusinessException("产品编码已存在");
        }
        return productRepository.save(new Product(code));
    }
}

…… 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值