mysql软删除唯一_软删除唯一索引设置及 Java Zero Date 解决办法

数据是无价的,当我们删除数据时,多数情况下我们会采用设置数据不可见的方法来替代真正意义上的物理删除,这种删除方法就叫做“软删除”。软删除可以保留数据的历史记录,便于今后数据分析以及一些故障情况下的恢复,但软删除也随之带来一些问题:数据表唯一索引失效。

# 唯一索引为什么会失效

很好理解,当我们使用软删除方法时,实际上是将删除delete from table 变为了 update table,虽然对外来说数据变为不可见了,但是数据库中的数据仍然保留,当插入同样的数据情况下,唯一索引就会发生冲突。

# 为逻辑删除字段添加联合索引

解决软删除索引问题的其中一个办法是用业务逻辑来保证数据的合法性,但是那样要大规模的修改插入逻辑,对系统影响较大。

另一个办法就是为我们的唯一字段和逻辑删除字段创建联合索引,这样就要求逻辑删除字段必须是一个动态值,可以是一个token、timestamp或是其他符合自己业务的值。

为什么逻辑删除字段是一个动态值?

考虑以下数据库

执行以下语句

可以看出,当只用一个固定值来代表逻辑删除状态时,联合索引会产生冲突,删除失败

有一个需要注意的坑是,MySQL中的联合索引是不能包含 NULL值的。例如:

在MySQL的官方文档对唯一索引的描述是这样的:

UNIQUE

A UNIQUE index creates a constraint such that all values in the index must be distinct. An error occurs if you try to add a new row with a key value that matches an existing row. If you specify a prefix value for a column in a UNIQUE index, the column values must be unique within the prefix length. A UNIQUE index permits multiple NULL values for columns that can contain NULL.

描述的最后写着,唯一索引会允许多个 NULL 值的添加。

因此当我们将逻辑删除字段加入唯一索引时,我们需要为其指定一个0值代表未删除的数据,而不能简单的使用NULL代替。

# Timestamp 0值设置与MySQL严格模式

一般情况下,我们会将逻辑删除字段设置为 timestamp 类型,当发生删除时,直接将字段设置为当前时间,这样既能实现删除字段值的动态变化,也能记录数据删除的时间,便于进行其他数据检索工作。

前面我们提到,逻辑删除字段需要指定一个0值,timestamp也有默认的0值。

timestamp 规定的数据大小是从1970-01-01 00:00:01.000000 到 2038-01-19 03:14:07.999999。其他任何不符合这个时间段的数据都会被变换为0插入到数据库中,这个数据在数据库中的表现为0000-00-00 00:00:00。

MySQL默认是禁止进行以上的转换的,如果直接部署一个MySQL服务并建立默认为0值的timestamp字段,或是插入一个0值的timestamp类型数据都会出错,这种模式被称为 MySQL 严格模式 (strict mode)。

默认的MySQL包含了6种模式(以MySQL 8.0 为准): 名称 意义 ONLY_FULL_GROUP_BY 对于 GROUP BY操作,如果在 SELECT 中的列没有出现在GROUP BY中出现,则拒绝

STRICT_TRANS_TABLE 如果操作中的值不能插入事务表,则拒绝该操作

NO_ZERO_IN_DATE 不允许日期和月份为0

NO_ZERO_DATE 不允许插入0日期

ERROR_FOR_DIVISION_BY_ZERO 如果数据被0除,则报错

NO_ENGINE_SUBTITUTION 如果储存引擎被禁用或未编译,则报错

我们可以通过以下命令查看当前SQL模式:

修改MySQL模式有两种方式: 修改 my.cnf 文件,在[mysqld]下加入:

使用 sql 语句修改 server:

# JAVA LocalDateTime 0值转换

当我们修改完数据库实现0值插入后,还需要对Java做一定的修改。

当我们直接进行数据映射时会发生以下错误

我们需要在mysql连接时添加zeroDateTimeBehavior=convertToNull,告知驱动对0值的处理方法,以 Spring 为例:

### 删除的实现方式 删除的核心在于通过某种机制标记数据为“已删除”,而不是将其从数据库中物理移除。以下是几种常见的实现方式及其优缺点分析。 --- #### 1. **数据库字段标记法** 这是最常用的删除实现方式之一,即在表结构中新增一个表示删除状态的字段(如 `is_deleted`),并通过更新该字段值来模拟删除行为[^3]。 ##### 数据库设计示例 ```sql CREATE TABLE articles ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(255) NOT NULL, content TEXT, is_deleted TINYINT DEFAULT 0 -- 添加此字段用于标记是否已被删除 ); ``` ##### 查询逻辑调整 对于查询操作,需额外过滤掉被标记为已删除的数据记录: ```sql SELECT * FROM articles WHERE is_deleted = 0; ``` ##### 删除操作修改 执行删除时只需更改对应的标志位即可: ```sql UPDATE articles SET is_deleted = 1 WHERE id = ?; ``` 这种方式简单直观,易于维护,但也可能存在一些潜在问题,例如唯一约束冲突等问题需要特别处理[^2]。 --- #### 2. **时间戳标记法** 另一种变体是在表中增加一个名为 `deleted_at` 的时间戳字段。如果某个记录未被删除,则该字段为空;一旦进行了删除操作,就为其赋值当前时间点。 ##### 表结构调整 ```sql ALTER TABLE articles ADD COLUMN deleted_at DATETIME NULL; ``` ##### 更新语句 ```sql UPDATE articles SET deleted_at = NOW() WHERE id = ?; ``` ##### 查询条件扩展 仅返回尚未被删除的记录: ```sql SELECT * FROM articles WHERE deleted_at IS NULL; ``` 相比单纯的布尔型字段,这种做法能够提供更多上下文信息(例如何时发生的删除动作),有助于后续数据分析或审计需求[^1]。 --- #### 3. **独立归档表法** 为了避免因大量删记录累积影响主表性能,可以创建一张单独的历史/归档表格专门存放这些废弃项。每当有新项目进入删除状态时便迁移过去。 ##### 创建归档表 ```sql CREATE TABLE article_archive AS SELECT * FROM articles LIMIT 0; -- 初始化架构一致的新表 ``` ##### 迁移过程 将目标行复制到归档表后立即从原始表中硬删除: ```sql INSERT INTO article_archive (id, title, content) SELECT id, title, content FROM articles WHERE id = ?; DELETE FROM articles WHERE id = ?; ``` 虽然增加了复杂度,却有效隔离了正常运转中的实体集与它们过期版本之间的干扰[^1]。 --- ### 解决删除相关问题 即使采用了上述任意一种方法实施删除策略,仍然可能面临某些挑战,下面列举几个典型场景及应对措施: - **唯一索引冲突**:当尝试重新插入曾经拥有相同属性组合的对象时容易违反UNIQUE限制。对此建议改用复合键或者允许NULL值参与比较运算[^2]。 - **级联效应管理**:考虑到现实世界里的对象往往相互关联紧密,单方面的变动很可能波及其他部分。因此有必要仔细规划涉及多方关系链路下的清理规程[^1]。 - **安全性考量**:既然保留下来的资料理论上依然可见可查,那么必须审慎对待权限界定事宜以防敏感机密泄露风险发生[^3]。 --- ### 总结 综上所述,无论是采用简单的标志列还是更复杂的分离式布局都可以达成预期效果——让看似消失不见的信息继续存活于系统内部供必要时刻调阅利用。然而具体选用哪一类取决于实际应用场景特点以及团队偏好等因素共同决定。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值