null调整为not null default xxx,不得不注意的坑

最近碰到一个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-OSC的实现原理

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

null调整为not null default xxx,不得不注意的坑

从原理图中可以看到,

  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和触发操作耦合在一起,相对来说,对业务有一定的侵入性。

  1. 既然触发器会将业务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
  1. 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。

Original: https://www.cnblogs.com/ivictor/p/14347901.html
Author: iVictor
Title: null调整为not null default xxx,不得不注意的坑

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/599270/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 数据类型拓展

    public class Demo03 { public static void main(String[] args) { //整数拓展 :进制 二进制0b 十进制 八进制0 十…

    数据库 2023年6月11日
    092
  • RocksDB上锁机制

    RocksDB作为一个开源的存储引擎支持事务的ACID特性,而要支持ACID中的I(Isolation),并发控制这块是少不了的,本文主要讨论RocksDB的锁机制实现,细节会涉及…

    数据库 2023年6月9日
    079
  • 23种设计模式之状态模式和策略模式的区别

    文章目录 概述 状态模式 策略模式 区别 总结 概述 在行为类设计模式中,状态模式和策略模式是亲兄弟,两者非常相似,我们先看看两者的通用类图,把两者放在一起比较一下 ; 状态模式 …

    数据库 2023年6月6日
    0133
  • 2021长安杯wp

    案件背景 2021年4月25日,上午8点左右,警方接到被害人金某报案,声称自己被敲诈数万元;经询问,昨日金某被嫌疑人诱导果聊,下载了某果聊软件,导致自己的通讯录和果聊视频被嫌疑人获…

    数据库 2023年6月11日
    085
  • 慢SQL治理方法论

    @ 一、背景 二、发现 三、定位 四、分析 4.1 索引层面分析 4.2 业务层面分析 五、解决 5.1 SQL优化 5.1.1索引优化 5.1.2 子查询优化 5.1.3 分页优…

    数据库 2023年5月24日
    0111
  • 计算机图形学

    计算机图形学 光追一般指光线追踪。 光线跟踪(也叫ray tracing或者光束投射法)。是一个在二维(2D)屏幕上呈现三维(3D)图像的方法。 BRDF是双向反射分布函数(Bid…

    数据库 2023年6月14日
    087
  • SNMP基础简介

    近来,公司产品开发涉及到SNMP方面的知识, 在此作一些总结,或许对您现在或者将来有用。 在目前越来越复杂的网络环境中,整个环境有各种各样的网络设备,为了能更好的对这些设备进行管理…

    数据库 2023年6月11日
    0101
  • Word书签替换,加盖电子印章及转换PDF(Java实用版)

    一、前言 在项目中有需要对word进行操作的,可以看看哈,本次使用比较强大的spire组件来对word进行操作,免费版支持三页哦,对于不止三页的word文件,可以购买收费版,官网:…

    数据库 2023年6月16日
    0104
  • 什么?MySQL 8.0 会同时修改两个ib_logfilesN 文件?

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源。 GreatSQL是MySQL的国产分支版本,使用上与MySQL一致。 作者介绍:孙黎,GreatDB …

    数据库 2023年6月11日
    0118
  • 在ESXI6.7中安装OpenWrt

    在ESXI6.7中安装OpenWrt 21.02.2 一、前置准备 安装好的esxi6.7 下载openwrt镜像,如:openwrt-21.02.2-x86-64-generic…

    数据库 2023年6月9日
    0133
  • leetcode 637. Average of Levels in Binary Tree 二叉树的层平均值(简单)

    一、题目大意 给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5 以内的答案可以被接受。 示例 1: 输入:root = [3,…

    数据库 2023年6月16日
    091
  • day03-拉取在线用户功能

    多用户即时通讯系统03 4.编码实现02 4.2功能实现-拉取在线用户 4.2.1思路分析 客户端想要知道在线用户列表,就要向服务器发送请求(Message),因为只有服务器端保持…

    数据库 2023年6月11日
    0177
  • Maven配置私有仓库

    前言 当公司或个人具有自己独有的jar时,不想公开,一般就会放在自己的私有Maven仓库中,在项目中需要引用,此时就需要将公司私有仓库配置到maven当中,一般我们的maven配置…

    数据库 2023年6月16日
    0160
  • Vue自定义组件实现v-model指令

    Tips: 本文所描述的Vue均默认是Vue2版本 在我们初次接触 Vue的时候,一定会了解到一个语法糖,那就是 v-model指令,它带给我们的第一印象就是它可以实现双向绑定 那…

    数据库 2023年6月11日
    091
  • 每个开发人员都应该关注的7个优秀的GitHub仓库

    1. FreeCodeCamp 2. Developer Roadmap 3. Awesome 4. Build Your Own X 5. Git Ignore 6. Syste…

    数据库 2023年6月11日
    0109
  • MySQL实战45讲 11

    11 | 怎么给字符串字段加索引? Q:如何在邮箱这样的字段上建立合理的索引? 用户表的定义: create table SUser( ID bigint unsigned pri…

    数据库 2023年6月16日
    097
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球