一条诡异的insert语句

问题背景

有同事反馈在mysql上面执行一条普通的insert语句,结果报错,
execute failed due to >>> Incorrect string value: ‘\xA1;offl...‘ for
column ‘biz_info‘ at row 1

经过半天的折腾,终于搞清楚了来龙去脉,这里简单给大家分享下。为了方便说明,我将测试例子中的表和语句简化,但不影响问题重现。

问题复现

连接字符集:UTF8

表结构:

CREATE TABLE `ggg` (
  `id` int(11) DEFAULT NULL,
  `c` varchar(100) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=gbk;

[email protected] 06:13:48>insert into ggg
values(1,concat(‘cardName:校园网‘,char(59),‘offlineCardType:campus‘));

Query OK, 1 row affected, 1 warning (2.51
sec)

[email protected] 06:14:36>show warnings\G

*************************** 1. row
***************************

Level: Warning

Code: 1366

Message: Incorrect string value:
‘\x91;offl...‘ for column ‘c‘ at row 1

查看结果

[email protected] 06:16:06>select * from ggg
where id=1;

*************************** 1. row
***************************

id: 1

c: cardName:鏍″洯缃

问题分析

从报错的结果来看,感觉是字符集转换引起的问题,而且由于连接串的字符集是UTF8,表的字符集是GBK,更容易引起怀疑。但是,即使是字符集转换,也不应该导致插入报错,因为语句中的中文字符“校园网"都是普通汉字,UTF8->GBK不应该存在问题。那我们在回过头来看看insert语句,唯一特殊的是使用了concat和char两个函数。会不会跟这两个函数有关系?char(59)实际是字符“;”,为了验证想法,做了两个实验:

  1. 将char(59)替换成‘;‘

insert into ggg values(1,concat(‘cardName:校园网‘,‘;‘,‘offlineCardType:campus‘));

2.设置连接串字符集为GBK

insert into ggg
values(1,concat(‘cardName:校园网‘,char(59),‘offlineCardType:campus‘));

果然,两种情况执行结果都是OK的,查询结果如下:

[email protected] 09:22:32>select * from
ggg\G

*************************** 1. row
***************************

id: 1
c: cardName:鏍″洯缃

*************************** 2. row
***************************

id: 1
c: cardName:校园网;offlineCardType:campus

*************************** 3. row
***************************

id: 1
c: cardName:校园网;offlineCardType:campus

跟踪了下源代码,找到了原因。char()函数返回的是一个binary类型字符串,在进行concat时,会导致‘cardName:校园网‘字符串到binary的转换。转换前,mysql将字符串‘cardName:校园网’看作是9个英文字符和3个汉字字符;转换后,mysql将其看作是18个字节的二进制串,其中,UTF8字符集的三个汉字“校园网”占了9个字节。由于目标表字符集是GBK,因此在入库时,还会发生一次binary到GBK的转码,“校园网”的二级制编码是E6A0A1 E59BAD E58DA1,在转码过程中,由于GBK字符集只包含一个字节(编码值<128)和二个字节的字符(汉字和特殊字符),“校园网”的二进制串会按照两个字节拆分E6A0 A1E5 9BAD E58D A1,前面四个变为“鏍″洯缃”,解析到A1时,由于A1既不是单字节字符,又不能与后面的字节组成一个合法的GBK字符,导致转换出错。
      现在就很好解释为啥改变语句后,两种情况都OK了。第一种情况,将char(59)直接替换成‘;’,由于不涉及UF8到binary的转换,只有utf8到gbk转码的过程,这个转换是OK的,不会出现乱码;第二种情况,将连接串的字符集设置为GBK,那么会涉及GBK到binary的转换,然后再从binary转换到GBK,由于整个转换过程并没有二进制数据丢失,所以也是OK的。

问题产生的两个关键点

  1. 连接字符集与表字符集不匹配
  2. 使用了char函数

解决办法

1.char函数提供了using语法来实现返回特定字符集的字符串,比如:char(59 using utf8)

2.保证连接字符集与表字符集一致。

时间: 2024-10-09 23:49:59

一条诡异的insert语句的相关文章

一条insert语句批量插入多条记录 AND 多条件游标更新 变CASE WHEN更新法提高速度

一条insert语句批量插入多条记录 常见的insert语句,向数据库中,一条语句只能插入一条数据: insert into persons (id_p, lastname , firstName, city ) values(204,'haha' , 'deng' , 'shenzhen'); (如上,仅插入了一条记录) 怎样一次insert插入多条记录呢? 使用示例: insert into persons (id_p, lastname , firstName, city ) values

laravel 获取上一条insert语句产生的id

<?php //頭部引入DB類 use Illuminate\Support\Facades\DB; //在方法中獲取获取上一条insert语句产生的id $id = DB::getPdo()->lastInsertId(); 原文地址:https://www.cnblogs.com/clubs/p/9936652.html

跟随一条insert语句, 进入TiDB的源码世界(上)

TiDB是google F1的开源实现: TiDB实现了基于mvcc的乐观锁,在线表结构变更,基于时间戳的数据线性一致性,等等: 为了可靠性,TiDB和Oracle一样,维护了百万级别的自动化测试用例,跑在k8s搭建的集群之上: TiDB里面用到的算法,都会写测试程序验证,以证明算法的正确性: 自 google F1开始,数据库被分成了两类: 一类是传统sql,代表如Oracle,依赖于高速网络和磁盘阵列来实现海量数据的扩容和数据的高可靠,成本及其高昂,大部分银行都用了Oracle的系统: 另一

如何判断一条sql(update,delete)语句是否执行成功

如何判断一条sql(update,delete)语句是否执行成功 catch  (SQLException    e)  {  }  catch不到错误应该就成功了.   ===================================================================== Statement对象提供了三种执行sql语句的方法.  1.ResultSet  executeQuery(String  sql)       执行查询语句,如ResultSet  r

MySQL INSERT语句

insert的语法 INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE] [INTO] tbl_name [(col_name,...)] VALUES ({expr | DEFAULT},...),(...),... [ ON DUPLICATE KEY UPDATE col_name=expr, ... ] 或: INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE] [IN

Mybatis执行insert语句,获取主键

一般的insert操作返回的是受影响行数,如果要获得新增加的那一行的主键,有两种方式可以实现. 方法1(推荐): <insert id="addAuthor" parameterType="domain.Author" useGeneratedKeys="true" keyProperty="id"> insert into author(username,password,email,bio) values(#{

“取出数据表中第10条到第20条记录”的sql语句+select top 使用方法

1.首先.select top使用方法: 參考问题  select top n * from和select * from的差别 select * from table --  取全部数据.返回无序集合 select top n * from table  -- 依据表内数据存储顺序取前n条,返回无序集合 select * from table order by id desc -- 取全部数据.按id逆序返回有序列表 select top n * from table order by id d

Mysql insert语句的优化

1) 如果你同时从同一客户插入很多行,使用多个值表的INSERT语句.这比使用分开INSERT语句快(在一些情况中几倍).    Insert into test values(1,2),(1,3),(1,4)- 2) 如果你从不同客户插入很多行,能通过使用INSERT DELAYED语句得到更高的速度.Delayed的含义是让insert 语句马上执行,其实数据都被放在内存的队列中,并没有真正写入磁盘:这比每条语句分别插入要快的多:LOW_PRIORITY刚好相反,在所有其他用户对表的读写完后

sql插入多条数据的sql语句

sql插入多条数据的sql语句 有三种方法:1.InSert Into <表名>(列名)Select <列名>From <源表名>如:INSERT INTO TongXunLu (姓名,电话,电子邮件) SELECT SName,SAddress,SEmailFROM Students 这种方法需要先建好新表.2.Select (列名)Into <表名>From <源表名>如:SELECT Students.SName,Students.SAdd