玖叶教程网

前端编程开发入门

给商户多付款,元凶竟然是Mysql数据库隐式转换

前段时间,生产商付款应用发生了特别大的故障,多给商户付款,于是就各种定位,查看各种日志,并且将最近一段时间的mysql的binlogs日志都解析了,将所有涉及付款表和商户所有的付款交易sql都提取出来,在这里用的binlog2sql工具提取的sql语句,binlog2sql工具的使用,这里就不做介绍了。

最后定位到,原来是有一个OA工单,修改金额的sql语句出现的问题,因为隐式转换,多更新了一条记录。在这里,很多人就不明白,一个隐式转换怎么就多更新了生产上记录。下面来做一下复盘。

1.创建模拟表,并插入测试数据

CREATE TABLE `t_test` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `k` int(12) unsigned NOT NULL DEFAULT '0',
  `c` char(120) NOT NULL DEFAULT '',
  `pad` char(60) NOT NULL DEFAULT '',
  `paymont` double(10,2) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=10003 DEFAULT CHARSET=utf8 MAX_ROWS=1000000;

insert into t_test values(1,4992833,'83868641912','67847967377',100.01);
insert into t_test values(10001,83749480,'83868641912C','67847967377',100000.00);

数据插入完毕,看看表中数据情况

mysql> select * from t_test where id in (1,10001);
+-------+----------+--------------+-------------+-----------+
| id         | k              | c                         | pad                 | paymont   |
+-------+----------+--------------+-------------+-----------+
|     1      |  4992833 | 83868641912    | 67847967377 |       100.01 |
| 10001 | 83749480 | 83868641912C | 67847967377 | 100000.00 |
+-------+----------+--------------+-------------+-----------+
2 rows in set (0.00 sec)

2.更新paymont金额

由于应用功能不齐全,有时候需要数据库运维人员去更新生产上的记录。在这里的需要是,更新字段C值为83868641912的记录,金额字段paymont,从100.01变为200000。由于生产上的update语句,必须要根据主键进行更新,于是查询出对应的主键值

select id from t_test where c=83868641912;

到这里,大家有看出什么问题,估计有人看出,有的看不出,没关系,执行一下这个select语句,看看结果

mysql> select id from t_test where c=83868641912;
+-------+
| id    |
+-------+
|     1 |
| 10001 |
+-------+
2 rows in set, 1 warning (0.00 sec)

然后开发根据获取的ID值写了2个update语句,更新金额,可惜这个开发是新来的,不是很熟悉这块儿业务,只是完成领导交代的任务,写个更新语句,就没有细想,于是悲剧就产生了。

update t_test set paymont=200000 where id=1;
update t_test set paymont=200000 where id=10001;

生产上执行完上面2个语句,其结果可想而知

mysql> select * from t_test where id in (1,10001);
+-------+----------+--------------+-------------+-----------+
| id    | k        | c            | pad         | paymont   |
+-------+----------+--------------+-------------+-----------+
|     1      |  4992833 | 83868641912   | 67847967377  | 200000.00 |
| 10001 | 83749480 | 83868641912C | 67847967377 | 200000.00 |
+-------+----------+--------------+-------------+-----------+
2 rows in set (0.00 sec)

3.隐式转是元凶

下面来仔细分析一下

select id from t_test where c=83868641912;

字段C是char字符串类型,而83868641912却是整数,在mysql里,两个变量一起比较时,必须类型是一致的,由于83868641912是整数,于是mysql特别智能,人性化的将字段C也转换成整数,其形式如下所示

mysql> SELECT CAST('83868641912C' AS SIGNED);
+--------------------------------+
| CAST('83868641912C' AS SIGNED) |
+--------------------------------+
|                    83868641912 |
+--------------------------------+
1 row in set, 1 warning (0.00 sec)


mysql> SELECT CAST('83868641912' AS SIGNED);
+-------------------------------+
| CAST('83868641912' AS SIGNED) |
+-------------------------------+
|                   83868641912 |
+-------------------------------+
1 row in set (0.00 sec)

看到没,83868641912C和83868641912转换成整数之后,值是一样的。
大家在写sql语句的时候,一定要注意,不要有隐式转换,这不仅仅会导致条件不走索引,sql语句性能变差,而且还可能导致业务逻辑不正确。

最后,祝愿生产系统无bug.

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言