弱智的 MySQL NULL

本文揭示了MySQL中NULL值对唯一索引约束的影响,指出即使设置了复合唯一索引,NULL值可能导致数据冗余。作者强调了合理设置默认值和理解NULL值在SQL中的行为的重要性,同时还讨论了NULL值对统计和查询性能的影响,以及如何避免这些问题。

点击上方“服务端思维”,选择“设为星标”

回复”669“获取独家整理的精选资料集

回复”加群“加入全国服务端高端社群「后端圈」

作者 | 董泽润

出品 | 董泽润的技术笔记

MySQL 字段一定要 NOT NULL, 并且设置合理的 default 值!!!

MySQL 字段一定要 NOT NULL, 并且设置合理的 default 值!!!

MySQL 字段一定要 NOT NULL, 并且设置合理的 default 值!!!

重要的事情提前说三遍,靠人约束是不行的,SQL 上线平台一定要检查语句是否规范。直接一棒子打死,省得以后祸害人间 ^^

背景

这几天同事遇到小问题,明明表结构中有 Unique 复合唯一索引,但是数据居然有重复,百思不得其解。因为涉及 json 字段,所以稍走些弯路,以为是 json 引入的问题

这里强调一下,MySQL json 功能很弱,大家不要用,会有性能问题(去年分享过)。同时,table schema 本身是一种强约束,字段 json 大家都往里塞,新功能开发,服务易手几次,json 就成了下水道,没人说得清里面存的都是啥

这点很像开发写的 protobuf 或是 thrift IDL, 定义一个 Map 字段,以后新加字段直接写进 Map 里 ...

复现

mysql> show create table players\G
*************************** 1. row ***************************
       Table: players
Create Table: CREATE TABLE `players` (
  `id` int(10) unsigned NOT NULL,
  `delete_at` timestamp NULL DEFAULT NULL,
  `rname` varchar(30) DEFAULT NULL,
  `rage` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `delete_at` (`delete_at`,`rname`,`rage`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

这是表结构,delete_at,rname,rage 三个字段构成一个复合的唯一索引

mysql> select * from players;
+----+---------------------+-------+------+
| id | delete_at           | rname | rage |
+----+---------------------+-------+------+
|  4 | NULL                | aaaa  | NULL |
|  2 | NULL                | aaaa  |   19 |
|  3 | NULL                | aaaa  |   19 |
|  1 | 2021-09-09 00:00:00 | aaaa  |   19 |
+----+---------------------+-------+------+
4 rows in set (0.01 sec)

上面是测试数据,复现数据冗余,id 为 2,3 的两条数据是一样的。为什么?而且业务跑了这么久,也没出现什么问题

另外索引顺序也不对,delete_at 业务逻辑表示记录被删除的时间点,未被删除的话默认为 NULL

select * from players where delete_at is not null and rname='xx' and range=xx;

这是模拟的线上 SQL, 看出问题了吧,应该唯一性高的放到前面才对

分析

不绕弯子,数据冗余原因在于 NULL。在 2005 年的时候就有人提了 Bug unique index allows duplicates if at least one of the columns is null[1], 大家可以看看讨论,争议很多

我测试后也得出结论,现在的 MySQL 版本也有这个现象,并且与 NULL 值的索引顺序无关。但是官方并不认为这是一个 issue

为什么?因为 SQL92 标准[2] 定义了 NULL 不与任何值相等, NULL 只是代表 missing values

NULLs cannot equal anything else, so can't stop UNIQUE from being TRUE. For example, a series of rows containing {1,NULL,2,NULL,3} is UNIQUE. UNIQUE never returns UNKNOWN.

另外关于唯一索引也有相关描述,视觉上数据重复很正常

A UNIQUE Constraint makes it impossible to COMMIT any operation that would cause the unique key to contain any non-null duplicate values. (Multiple null values are allowed, since the null value is never equal to anything, even another null value.) A UNIQUE Constraint is violated if its condition is FALSE for any row of the Table it belongs to.

使用问题

抛开上文说的索引问题,MySQL 官方文档也指出了 NULL 使用让人困惑的地方 Working with NULL Values[3] 和 Problems with NULL Values[4]

mysql> select count(*), count(delete_at) from players;
+----------+------------------+
| count(*) | count(delete_at) |
+----------+------------------+
|        4 |                1 |
+----------+------------------+
1 row in set (0.00 sec)

count(*) 和 count(column) 是不一样的,后者会过滤掉 NULL

mysql> SELECT 1 = NULL, 1 <> NULL, 1 < NULL, 1 > NULL;
+----------+-----------+----------+----------+
| 1 = NULL | 1 <> NULL | 1 < NULL | 1 > NULL |
+----------+-----------+----------+----------+
|     NULL |      NULL |     NULL |     NULL |
+----------+-----------+----------+----------+
1 row in set (0.01 sec)

mysql> select NULL = NULL, 1 is NULL, 1 is NOT NULL;
+-------------+-----------+---------------+
| NULL = NULL | 1 is NULL | 1 is NOT NULL |
+-------------+-----------+---------------+
|        NULL |         0 |             1 |
+-------------+-----------+---------------+
1 row in set (0.00 sec)

可以看到,只能使用 is NULL 或者 is NOT NULL 来判断是否等于 NULL

反思

除了上面使用的困惑,NULL 值过多会影响统计信息,可能影响执行计划。MySQL 很不负责的把对 NULL 值的统计方式交给了用户 innodb_stats_method, 默认值是 nulls_equal

Specifies how InnoDB index statistics collection code should treat NULLs. Possible values are NULLS_EQUAL (default), NULLS_UNEQUAL and NULLS_IGNORED

  • 当变量设置为 nulls_equal 时,所有 NULL 值都被视为相同(即,它们都形成一个 value group)

  • 当变量设置为 nulls_unequal 时,NULL 值不被视为相同。相反,每个 NULL value 形成一个单独的 value group, 大小为 1

  • 当变量设置为 nulls_ignored 时,将忽略 NULL 值

阿里也有过一篇文章,讲 unique 索引有 NULL 值导致主备延迟[5] 感兴趣的可以看看

最骚的是有人,给一个字段赋值 'NULL', 注意是字符串 'NULL' 不是 NULL

小结

写到这里,想说一下本文有什么价值嘛?好像没有,直接禁止完事,哪会引起那么多问题。也咨询了几个小伙伴,大家只记得不让用 NULL, 都忘了为什么 ^_^

今天的分享就这些,写文章不容易,如果对大家有所帮助和启发,请大家帮忙点击再看点赞分享 三连

关于 MySQL NULL 大家有什么看法,欢迎留言一起讨论,大牛多留言 ^_^

— 本文结束 —

● 漫谈设计模式在 Spring 框架中的良好实践

● 颠覆微服务认知:深入思考微服务的七个主流观点

● 人人都是 API 设计者

● 一文讲透微服务下如何保证事务的一致性

● 要黑盒测试微服务内部服务间调用,我该如何实现?

关注我,回复 「加群」 加入各种主题讨论群。

对「服务端思维」有期待,请在文末点个在看

喜欢这篇文章,欢迎转发、分享朋友圈

在看点这里

### 对大型预训练模型进行微调的方法 对于大型预训练模型的微调,存在多种策略来优化性能并减少资源消耗。具体来说: #### 微调方式的选择 有三种主要类型的微调方法适用于不同的场景和需求[^1]。 - **仅预测模式** 这是最简单的形式,其中预先训练好的模型被用于直接做预测而不改变其任何参数。这种方式适合于源域与目标任务非常相似的情况,在这种情况下能够快速获得较好的效果而无需额外训练时间。 - **部分微调(迁移学习)** 此方法涉及冻结大部分网络权重,只调整最后一层或多层以适应新任务的需求。这通常意味着更改全连接层(fc layer),以便它可以处理新的类别数。这种方法既利用了原始模型强大的特征提取能力又减少了过拟合的风险。 - **全面微调** 当希望进一步改进模型表现时可以选择此选项,即不仅更新顶层还解冻一些甚至全部卷积层来进行端到端的学习。虽然这样做会显著增加计算成本,但对于那些拥有充足算力并且追求更高准确性的项目而言可能是值得的投资。 #### Adapter-Tuning 方法简介 除了上述传统意义上的微调外,还有专门设计用来应对大规模预训练模型特殊挑战的技术——比如 Adapter-Tuning 。这是一种轻量级方案,通过向现有架构内部添加小型辅助结构(称为适配器) 来实现高效的任务定制化改造[^3]。这些新增组件允许在不影响整体稳定性的同时引入必要的灵活性去捕捉特定领域内的细微差别。 ```python class Adapter(nn.Module): def __init__(self, input_size=768, adapter_size=64): super().__init__() self.down_project = nn.Linear(input_size, adapter_size) self.up_project = nn.Linear(adapter_size, input_size) def forward(self, hidden_states): adjusted_hidden_state = self.down_project(hidden_states) nonlinearity_applied = torch.relu(adjusted_hidden_state) output = self.up_project(nonlinearity_applied) return output + hidden_states ``` 这段代码展示了如何构建一个基本版本的 Adapter 层,它接受来自 BERT 或其他 Transformer 架构中的隐藏状态作为输入,并经过两次线性变换以及 ReLU 激活函数作用后再返回给原位置继续传播下去。 #### 实践建议 当决定采用哪种方式进行微调时应考虑多个因素,包括但不限于可用硬件条件、预期精度水平及开发周期长短等实际限制条件。如果目标是在短时间内得到满意的结果,则可能更倾向于选择前两种较为保守的方式;而对于长期研究或工业界应用则不妨尝试更加激进但也更有潜力取得突破进展的做法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值