最近碰到一个case,值得分享一下。

现象

一个DDL,将列的属性从null调整为not null default xxx,

alter table slowtech.t1 modify name varchar(10) not null default 'slowtech';

通过平台执行(平台调用的是pt-online-schema-change)。

但在执行的过程中,业务SQL报错,提示“ERROR 1048 (23000): Column 'name' cannot be null”。

在剖析具体的问题之前,首先,我们看看pt-online-schema-change的原理。

PT-OSC的实现原理

从原理图中可以看到,

1.  对于全量数据的同步,pt-online-schema-change是以chunk为单位分批来拷贝的。

2.  对于增量数据的同步,pt-online-schema-change是通过触发器来实现的。

结合pt-online-schema-change的原理,我们来重现下问题场景。

mysql> create table slowtech.t1(id int primary key,name varchar(10));

mysql> create table slowtech._t1_new(id int primary key,name varchar(10));

mysql> alter table slowtech._t1_new modify name varchar(10) not null default 'slowtech';

mysql> create trigger slowtech.`pt_osc_slowtech_t1_ins` after insert on `slowtech`.`t1` for each row replace into `slowtech`.`_t1_new` (`id`, `name`) values (new.`id`, new.`name`);

mysql> insert into slowtech.t1(id) values(1);
ERROR 1048 (23000): Column 'name' cannot be null

问题完美呈现,有的童鞋可能会有疑问,t1的name列默认不是null么?为什么不允许null值的插入?

问题原因

问题出在触发器上面。

触发器会将业务SQL(“insert into slowtech.t1(id) values(1)”)和触发操作(“replace into slowtech._t1_new (id, name) values(1, null)”)放到一个事务内执行。

“insert into slowtech.t1(id) values(1)”并不违反t1表的约束,但违反了_t1_new表的约束。

通过上面的分析,我们得到了两点启示:

1.  类似DDL(将列的属性从null修改为not null default 'abc')要注意。

从原理上看,既然涉及到全量数据+增量数据的同步,都会存在这种问题,不单单是pt-online-schema-change,包括Online DDL,gh-ost同样如此。

只不过,触发器这种方案会将业务SQL和触发操作耦合在一起,相对来说,对业务有一定的侵入性。

2. 既然触发器会将业务SQL和触发操作放到一个事务内执行,如果pt-online-schema-change异常退出,留下了触发器和中间表(_t1_new),在清理现场时,应首先删除触发器,再删除中间表。

如果首先删除中间表,会导致针对原表的所有DML操作失败。

mysql> drop table slowtech._t1_new;

mysql> insert into slowtech.t1 values(1,'victor');
ERROR 1146 (42S02): Table 'slowtech._t1_new' doesn't exist

数据拷贝也有坑

在执行DDL之前,还有一段小插曲。

在执行DDL之前,开发提单将该列的null值修改为了默认值。这样就导致了,问题是在业务SQL插入的过程中暴露的,而不是在数据拷贝过程中暴露。

在数据拷贝的过程中,如果拷贝的数据中,该列存在null值,pt-online-schema-change会直接报错退出。

mysql> create table slowtech.t1(id int primary key,name varchar(10));

mysql> insert into slowtech.t1(id) values(1);

# pt-online-schema-change h=xxxxx,u=root,p=123456,D=slowtech,t=t1 --alter "modify name varchar(10) not null default 'slowtech'" --execute
No slaves found. See --recursion-method if host xxxx has slaves.
Not checking slave lag because no slaves were found and --check-slave-lag was not specified.
Operation, tries, wait:
analyze_table, 10, 1
copy_rows, 10, 0.25
create_triggers, 10, 1
drop_triggers, 10, 1
swap_tables, 10, 1
update_foreign_keys, 10, 1
Altering `slowtech`.`t1`...
Creating new table...
Created new table slowtech._t1_new OK.
Altering new table...
Altered `slowtech`.`_t1_new` OK.
2020-09-07T09:13:25 Creating triggers...
2020-09-07T09:13:25 Created triggers OK.
2020-09-07T09:13:25 Copying approximately 1 rows...
2020-09-07T09:13:25 Dropping triggers...
2020-09-07T09:13:25 Dropped triggers OK.
2020-09-07T09:13:25 Dropping new table...
2020-09-07T09:13:25 Dropped new table OK.
`slowtech`.`t1` was not altered.
(in cleanup) 2020-09-07T09:13:25 Error copying rows from `slowtech`.`t1` to `slowtech`.`_t1_new`: 2020-09-07T09:13:25 Copying rows caused a MySQL error 1048:
Level: Warning
Code: 1048
Message: Column 'name' cannot be null
Query: INSERT LOW_PRIORITY IGNORE INTO `slowtech`.`_t1_new` (`id`, `name`) SELECT `id`, `name` FROM `slowtech`.`t1` LOCK IN SHARE MODE /*pt-online-schema-change 9234 copy table*/
2020-09-07T09:13:25 Dropping triggers...
2020-09-07T09:13:25 Dropped triggers OK.
`slowtech`.`t1` was not altered.

上述报错,pt-online-schema-change加个参数即可规避(--null-to-not-null)。

在实现上,该参数会忽略1048错误,此时,对于字符类型的列,会填充空字符,对于数字类型的列,会填充0。

mysql> create table slowtech.t1(id int primary key,name varchar(10));

mysql> create table slowtech._t1_new(id int primary key,name varchar(10));

mysql> alter table slowtech._t1_new modify name varchar(10) not null default 'slowtech';

mysql> insert into slowtech.t1(id) values(1);

mysql> select * from slowtech.t1;
+----+------+
| id | name |
+----+------+
| 1 | NULL |
+----+------+
1 row in set (0.00 sec) mysql> insert low_priority ignore into slowtech._t1_new (id, name) select id, name from slowtech.t1 lock in share mode;
Query OK, 1 row affected, 1 warning (0.01 sec)
Records: 1 Duplicates: 0 Warnings: 1 mysql> show warnings;
+---------+------+------------------------------+
| Level | Code | Message |
+---------+------+------------------------------+
| Warning | 1048 | Column 'name' cannot be null |
+---------+------+------------------------------+
1 row in set (0.00 sec) mysql> select * from slowtech._t1_new;
+----+------+
| id | name |
+----+------+
| 1 | |
+----+------+
1 row in set (0.00 sec)

所以,线上使用该参数要注意,要确认被填充的值是否符合自己的预期行为。

从目前的分析来看,要将一个列的属性从null直接修改为not null default xxx,几乎是不可能的,除非:

1.  该列不存在null值。

2.  在DDL的过程中,没有类似于“insert into slowtech.t1(id) values(1)”的业务SQL出现。

结论

很显然,这两个条件很难同时满足。既然如此,这个需求还能实现吗?能!只不过比较复杂。

下面,看看具体的实施步骤。

1. 首先,将列的属性调整为null default xxx,这样做的目的是为了避免增量同步过程中,类似“insert into slowtech.t1(id) values(1)”的业务SQL,产生新的null值。

2. 其次,手动将null值调整为默认值。需要注意的是,如果记录数较多,这一步的操作难度也是极大的。

3. 最后,将列的属性调整为not null default xxx。

对于not null default xxx的正确理解

在很多数据库规范里面,都推荐将列定义为not null default xxx,但很多童鞋,对这段定义的实际效果却相当模糊。

下面具体来说说,这段定义的实际作用。这段定义实际上由两部分组成:

1.  not null,约束,指的是不可显式插入null值,如,

mysql> create table slowtech.t1(id int primary key,name varchar(10) not null default 'slowtech');

mysql> insert into slowtech.t1 values(1,null);
ERROR 1048 (23000): Column 'name' cannot be null

2.  default 'slowtech',如果在插入时,没有显式指定值,则以默认值填充。

mysql> insert into slowtech.t1(id) values(1);

mysql> select * from slowtech.t1;
+----+----------+
| id | name |
+----+----------+
| 1 | slowtech |
+----+----------+
1 row in set (0.00 sec)

可以看到,这两部分其实没有任何关系,对于一个列,我们同样可以定义为null default xxx。

null调整为not null default xxx,不得不注意的坑的更多相关文章

  1. mysql创建表时,设置timestamp DEFAULT NULL报错1067 - Invalid default value for 'updated_at'

    问题背景: 线上的linux服务器上的mysql服务器中导出数据库的结构.想要在本地创建一个测试版本 导出后再本地mysql上运行却报错   1067 - Invalid default value ...

  2. [Guava官方文档翻译] 2.使用和避免使用null (Using And Avoiding Null Explained)

    本文地址:http://www.cnblogs.com/hamhog/p/3536647.html "null很恶心." -Doug Lea "这是一个令我追悔莫及的错误 ...

  3. MyBatis resultType用Map 返回值中有NULL则缺少字段 返回值全NULL则map为null

    这个问题我大概花了2个小时才找到结果 总共需要2个设置 这里是对应springboot中的配置写法 @select("select sum(a) a,sum(b) b from XXX wh ...

  4. mysql 优化之 is null ,is not null 索引使用测试

    关于mysql优化部分,有很多网友说尽量避免使用is null, is not null,select * 等,会导致索引失效,性能降低?那是否一定收到影响呢?真的就不会使用索引了吗? 本文的测试数据 ...

  5. SQL SERVER 中is null 和 is not null 将会导致索引失效吗?

    其实本来这个问题没有什么好说的,今天优化的时候遇到一个SQL语句,因为比较有意思,所以我截取.简化了SQL语句,演示给大家看,如下所示 declare @bamboo_Code varchar(3); ...

  6. null和空 not null

    所谓的NULL就是什么都没有,连\0都没有,\0在字符串中是结束符,但是在物理内存是占空间的,等于一个字节,而NULL就是连这一个字节都没有.在 数据库里是严格区分的,任何数跟NULL进行运算都是NU ...

  7. SQL - 将NULL设置为 NOT NULL

    在有些时候,我们在创建表的时候,会忘掉设置某一个列的属性,比如忘了设置为非空,但是默认情况下系统会自动的设置为NULL.这样我们该怎样通过语句设置呢?? 例如有一个表,其中的一个属性为WM CHAR( ...

  8. [转]Laravel - Where null and Where not null eloquent query example

    本文转自: https://hdtuto.com/article/laravel-where-null-and-where-not-null-eloquent-query-example- if yo ...

  9. 关于MSSQL中IS NULL和IS NOT NULL的问题

    在SQL语句中我们一般会避免写IS NULL和IS NOT NULL,因为这样优化器不会使用索引. 但经过一系列测试发现这句话并不完全对,因为有时候也会使用索引. 语句: select * from ...

随机推荐

  1. 阿里面试:问springBoot自动装配我这样回答的,面试官对我竖起了大拇指

    引言 最近有个读者在面试,面试中被问到了这样一个问题"看你项目中用到了springboot,你说下springboot的自动配置是怎么实现的?"这应该是一个springboot里面 ...

  2. [LeetCode]实现数学计算

    乘方 思路是:pow(x,n) = pow(x,n/2)*pow(x,n-n/2) 递归实现 public double myPow(double x, int n) { if (n==0) retu ...

  3. Redis主从复制getshell技巧

    Redis未授权漏洞常见的漏洞利用方式: Windows下,绝对路径写webshell .写入启动项. Linux下,绝对路径写webshell .公私钥认证获取root权限 .利用contrab计划 ...

  4. 使用jmeter进行压力测试与nginx连接数优化

    案例训练目标 学会使用jmeter工具 学会配置nginx连接数优化 包含技能点 使用jmeter做压力测试 配置nginx的并发连接数 环境要求 PC支持VT,4G内存以上:vmware虚拟机安装有 ...

  5. 前端面试题归类-css

    一.说下盒模型? 有两种盒模型,W3C盒模型和IE盒模型通常说的"IE盒子模型"指的是IE5.5,IE6及其以后,盒模型都为 content-box当浏览器未设置<!doct ...

  6. RocketMQ(十):数据存储模型设计与实现

    消息中间件,说是一个通信组件也没有错,因为它的本职工作是做消息的传递.然而要做到高效的消息传递,很重要的一点是数据结构,数据结构设计的好坏,一定程度上决定了该消息组件的性能以及能力上限. 1. 消息中 ...

  7. 死磕以太坊源码分析之MPT树-上

    死磕以太坊源码分析之MPT树-上 前缀树Trie 前缀树(又称字典树),通常来说,一个前缀树是用来存储字符串的.前缀树的每一个节点代表一个字符串(前缀).每一个节点会有多个子节点,通往不同子节点的路径 ...

  8. 解析SwiftUI布局细节(三)地图的基本操作

    前言 前面的几篇文章总结了怎样用 SwiftUI 搭建基本框架时候的一些注意点(和这篇文章在相同的分类里面,有需要了可以点进去看看),这篇文章要总结的东西是用地图数据处理结合来说的,通过这篇文章我们能 ...

  9. sa-token v1.9.0 版本已发布,带来激动人心新特性:同端互斥登录

    sa-token是什么? sa-token是一个JavaWeb轻量级权限认证框架, 官网首页:http://sa-token.dev33.cn/ 如果你经常使用腾讯QQ,就会发现它的登录有如下特点:它 ...

  10. Azure Cost alerts 花费警报

    一,引言 2020已完结,迎来了2021年新的开始.Allen 在新的一年中继续分享自己所学习到的 Azure 技术.本篇文章的内容也不多,也是一个网友遇到的一个问题----- Azure 上有没有花 ...