Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > mysql列属性从null改为not null default

MySQL列属性从NULL改为NOT NULL DEFAULT的深坑与完美解决方案

作者:Seal^_^

在MySQL数据库中,将列的属性从NULL改为NOT NULL DEFAULT是一个常见的需求,尤其是在数据完整性要求较高的场景下,这个过程看似简单,但实际上可能会遇到一些深坑(常见问题),特别是在生产环境中操作时,以下是一些可能遇到的深坑及其解决方案

引言

在MySQL数据库的日常运维中,我们经常需要修改表结构。其中一种常见的需求是:将列的属性从允许NULL改为NOT NULL并设置默认值。

ALTER TABLE slowtech.t1 MODIFY name VARCHAR(10) NOT NULL DEFAULT 'slowtech';

看似简单的操作,却可能引发严重的线上故障。本文将深入剖析这个操作背后的陷阱,并给出安全的实施步骤。

1. 问题现象

1.1 业务突然报错

某天,业务突然报错:

ERROR 1048 (23000): Column 'name' cannot be null

但奇怪的是,业务执行的SQL明明是:

INSERT INTO slowtech.t1(id) VALUES(1);  -- 没有插入name字段!

按照常理,这条SQL不应该报错,因为:

1.2 现场排查

检查表结构:

mysql> DESC slowtech.t1;
+-------+-------------+------+-----+----------+-------+
| Field | Type        | Null | Key | Default  | Extra |
+-------+-------------+------+-----+----------+-------+
| id    | int(11)     | NO   | PRI | NULL     |       |
| name  | varchar(10) | NO   |     | slowtech |       |  -- 已改为NOT NULL
+-------+-------------+------+-----+----------+-------+

原来,就在不久前,DBA执行了一个DDL操作,将name列从NULL改为了NOT NULL DEFAULT。

2. 深入分析:为什么会出现这个错误?

2.1 PT-OSC的实现原理

问题出在DDL工具的执行方式上。以pt-online-schema-change为例,其原理如下:

2.2 问题重现

-- 1. 原始表结构(允许NULL)
CREATE TABLE slowtech.t1(
    id INT PRIMARY KEY,
    name VARCHAR(10)
);

-- 2. PT-OSC创建新表并修改结构
CREATE TABLE slowtech._t1_new(
    id INT PRIMARY KEY,
    name VARCHAR(10) NOT NULL DEFAULT 'slowtech'  -- 已修改
);

-- 3. 创建触发器
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`);

-- 4. 业务插入数据(只插入id)
INSERT INTO slowtech.t1(id) VALUES(1);
-- 报错:ERROR 1048 (23000): Column 'name' cannot be null

2.3 为什么会报错?

关键点:触发器会将业务SQL和触发操作放在同一个事务中执行。业务SQL本身不违反原表约束,但触发操作违反了新表的NOT NULL约束。

3. 数据拷贝阶段的另一个坑

3.1 拷贝过程中的报错

如果原表中已存在NULL值,PT-OSC在拷贝数据时也会报错:

-- 原表存在NULL值
mysql> INSERT INTO slowtech.t1(id) VALUES(1);
mysql> SELECT * FROM t1;
+----+------+
| id | name |
+----+------+
|  1 | NULL |
+----+------+
-- 执行PT-OSC
pt-online-schema-change h=localhost,D=slowtech,t=t1 \
  --alter "MODIFY name VARCHAR(10) NOT NULL DEFAULT 'slowtech'" \
  --execute
-- 报错:
-- Error copying rows from `slowtech`.`t1` to `slowtech`.`_t1_new`: 
-- Column 'name' cannot be null

3.2 使用–null-to-not-null参数的隐患

-- 添加参数可以忽略1048错误
pt-online-schema-change ... --null-to-not-null --execute
-- 实际执行的是:
INSERT LOW_PRIORITY IGNORE INTO `slowtech`.`_t1_new` (`id`, `name`) 
SELECT `id`, `name` FROM `slowtech`.`t1` LOCK IN SHARE MODE;
-- 查看结果
mysql> SELECT * FROM _t1_new;
+----+------+
| id | name |
+----+------+
|  1 |      |  -- NULL被转换为空字符串!
+----+------+

⚠️ 重要警告:--null-to-not-null会将NULL值转换为:

这些默认值是否符合业务预期,必须仔细确认!

4. 正确的实施步骤

4.1 总体流程

4.2 详细实施步骤

步骤1:先改为NULL DEFAULT

-- 先允许NULL,但设置默认值
ALTER TABLE slowtech.t1 
MODIFY name VARCHAR(10) NULL DEFAULT 'slowtech';

作用:确保增量数据不再产生新的NULL值(未指定该列时会填充默认值)。

步骤2:处理现有NULL值

-- 查看现有NULL值数量
SELECT COUNT(*) FROM slowtech.t1 WHERE name IS NULL;

-- 分批更新NULL值为默认值(避免锁大表)
-- 每次更新1000条
UPDATE slowtech.t1 
SET name = DEFAULT 
WHERE name IS NULL 
LIMIT 1000;

-- 确认没有NULL值了
SELECT COUNT(*) FROM slowtech.t1 WHERE name IS NULL;

注意事项:

步骤3:最终改为NOT NULL DEFAULT

-- 确认没有NULL值后,执行最终修改
ALTER TABLE slowtech.t1 
MODIFY name VARCHAR(10) NOT NULL DEFAULT 'slowtech';

4.3 使用PT-OSC的安全方案

如果必须使用PT-OSC,可以这样操作:

# 第一步:先改为NULL DEFAULT
pt-online-schema-change h=localhost,D=slowtech,t=t1 \
  --alter "MODIFY name VARCHAR(10) NULL DEFAULT 'slowtech'" \
  --execute

# 第二步:手动更新NULL值(同上)
mysql -e "UPDATE slowtech.t1 SET name = DEFAULT WHERE name IS NULL LIMIT 1000"

# 第三步:改为NOT NULL DEFAULT
pt-online-schema-change h=localhost,D=slowtech,t=t1 \
  --alter "MODIFY name VARCHAR(10) NOT NULL DEFAULT 'slowtech'" \
  --execute

5. 理解NOT NULL DEFAULT的真正含义

很多开发者对NOT NULL DEFAULT的理解存在误区:

-- 创建表
CREATE TABLE t1 (
    id INT PRIMARY KEY,
    name VARCHAR(10) NOT NULL DEFAULT 'slowtech'
);

5.1 误区一:认为DEFAULT会覆盖NULL

-- ❌ 错误理解:认为会自动将NULL转为默认值
INSERT INTO t1 VALUES (1, NULL);  
-- 实际结果:ERROR 1048 (23000): Column 'name' cannot be null

5.2 误区二:认为NOT NULL和DEFAULT是一体的

-- ✅ 正确理解:这是两个独立的部分
CREATE TABLE t2 (
    name1 VARCHAR(10) NOT NULL DEFAULT 'a',  -- NOT NULL + DEFAULT
    name2 VARCHAR(10) NULL DEFAULT 'b',      -- NULL + DEFAULT
    name3 VARCHAR(10) NOT NULL,               -- NOT NULL + 无DEFAULT
    name4 VARCHAR(10) NULL                    -- NULL + 无DEFAULT
);

5.3 各组合的实际效果

定义插入NULL不指定该列插入其他值
NOT NULL DEFAULT 'a'❌ 报错✅ 填’a’✅ 正常
NULL DEFAULT 'a'✅ 填NULL✅ 填’a’✅ 正常
NOT NULL❌ 报错❌ 报错✅ 正常
NULL✅ 填NULL✅ 填NULL✅ 正常

6. PT-OSC使用注意事项

6.1 异常退出后的清理顺序

如果PT-OSC异常退出,留下了触发器和中间表,清理时必须注意顺序:

-- ❌ 错误顺序:先删中间表
DROP TABLE slowtech._t1_new;

-- 此时业务执行DML会报错
INSERT INTO slowtech.t1 VALUES (1, 'victor');
-- ERROR 1146 (42S02): Table 'slowtech._t1_new' doesn't exist

-- ✅ 正确顺序:先删触发器
DROP TRIGGER slowtech.`pt_osc_slowtech_t1_ins`;

-- 再删中间表
DROP TABLE slowtech._t1_new;

6.2 关键参数说明

参数作用风险
--null-to-not-null忽略1048错误,将NULL转为默认值转换后的值可能不符合预期
--no-swap-tables只创建新表不交换用于调试,不会修改原表
--chunk-size控制每次拷贝的行数太小影响速度,太大影响性能

7. 最佳实践总结

7.1 安全三步曲

7.2 操作检查清单

7.3 一句话总结

将列从NULL改为NOT NULL DEFAULT,真正的难点不在于DDL本身,而在于如何安全地处理现有NULL值和增量数据中的潜在NULL值。

理解这一点,才能避免踩坑,安全地完成这个看似简单的操作。

到此这篇关于MySQL列属性从NULL改为NOT NULL DEFAULT的深坑与完美解决方案的文章就介绍到这了,更多相关mysql列属性从null改为not null default内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文