有开发同事在生产环境对某个表将int字段改成decimal(10,4)后,提示 1264 Out of range value报错,且原来值为负数,更改字段类型后,全部变为0,咨询dba这个为什么会报错,对数据有什么影响?
我收到这问题后,第一反应就是由于字段类型转换,长度不够发生了数据溢出。
于是,下面对模拟该问题进行测试:
1.建测试表
CREATE TABLE `zeng1` (
`_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`a` int(32) DEFAULT NULL,
PRIMARY KEY (`_id`)
) ENGINE=InnoDB AUTO_INCREMENT=28129 DEFAULT CHARSET=utf8 ;
2.准备测试用数据
insert into zeng1(a) values (70000);
insert into zeng1(a) values (-123);
insert into zeng1(a) values (-10);
select * from zeng1;
+-------+-------+
| _id | a |
+-------+-------+
| 28129 | 70000 |
| 28130 | -123 |
| 28131 | -10 |
+-------+-------+
3.修改字段类型
alter table zeng1 modify column a decimal(10,4) unsigned;
Query OK, 3 rows affected, 2 warnings (0.03 sec)
Records: 3 Duplicates: 0 Warnings: 2
4.查看warnings
(product)root@localhost [midea_gls]> show warnings;
+---------+------+--------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------+
| Warning | 1264 | Out of range value for column 'a' at row 2 |
| Warning | 1264 | Out of range value for column 'a' at row 3 |
+---------+------+--------------------------------------------+
2 rows in set (0.00 sec)
上面显示第2和第3行发生了数据溢出。
5.查询类型更改后的数据
select * from zeng1;
+-------+------------+
| _id | a |
+-------+------------+
| 28129 | 70000.0000 |
| 28130 | 0.0000 |
| 28131 | 0.0000 |
+-------+------------+
3 rows in set (0.00 sec)
第四和第五行的a字段记录发生了溢出。应更改将字段定义为带符号的,修改字段如下:
alter table zeng1 modify column a decimal(10,4);
再把数据update:
update zeng1 set a=-123 where `_id`= 28130;
update zeng1 set a=-10 where `_id`= 28131;
查看变为正常了:
select * from zeng1;
+-------+------------+
| _id | a |
+-------+------------+
| 28129 | 70000.0000 |
| 28130 | -123.0000 |
| 28131 | -10.0000 |
+-------+------------+
3 rows in set (0.00 sec)
decimal(10,4)的取值范围为-999999.9999到999999.9999,decimal(10,4)中的10表示全部位数为10(整数位+小数位),4表示小数位。
show warnings看到out of range表示超过了(小于最小值或大于最大值)这个值 ,建议生产环境在对一个表更改字段前,先对这个表的数据create table as select 来备份一份,若出现out of range时,
该记录的数据将全部变成0或9。
1.确保decimal(M,N)中M-N>=max(length(a)),在更改前可以查询字段a值的最大长度:
select max(length(a)) from zeng1;
2.确保有负数值的字段定义为带符号的,decmial默认为带符号,即不加 unsigned。检查字段a是否有负数值,用下面查询检查:
select count(*) from zeng1 where a like '-%',若返回值大于0,表示存在负数。
若前面在更改字段前,先对表进行了备份,表名为zeng1_bak,更改字段后,如何将数据更新回去?
select * from zeng1;
+-------+------------+
| _id | a |
+-------+------------+
| 28129 | 70000.0000 |
| 28130 | 0.0000 |
| 28131 | 0.0000 |
+-------+------------+
3 rows in set (0.00 sec)
select * from zeng1_bak;
+-------+-------+
| _id | a |
+-------+-------+
| 28129 | 70000 |
| 28130 | -123 |
| 28131 | -10 |
+-------+-------+
更新如下:
update zeng1 a1 set a1.a=(
select b1.a from zeng1_bak b1 where b1.`_id`=a1.`_id` and b1.`_id` in (28130,28131));
select * from zeng1;
+-------+------------+
| _id | a |
+-------+------------+
| 28129 | 70000.0000 |
| 28130 | -123.0000 |
| 28131 | -10.0000 |
+-------+------------+
3 rows in set (0.00 sec)