【线上踩坑分享】MySQL GROUP_CONCAT函数超长截断问题

448 阅读3分钟

前言

MySQL中,你可以通过GROUP_CONCAT函数获取一个将所有字符串串联在一起的新字符串,其语法定义如下:

GROUP_CONCAT([DISTINCT] expr [,expr ...] 
[ORDER BY {unsigned_integer | col_name | expr} 
[ASC | DESC] [,col_name ...]] 
[SEPARATOR str_val])

该函数的作用经常可以用于将多列数据合并为一列返回,方便客户端拿到后直接使用。

演示示例

我们可以简单的演示一下其使用效果。

准备一张数据表。

image.png

执行如下查询语句:select name,GROUP_CONCAT(age) from t1 GROUP BY name;

返回结果:

image.png

高级语法

从语法定义上来,GROUP_CONCAT还在拼接字符串时还能提供一些额外的能力,比如拼接后的字符串可以排序、去重、按指定分隔符拼接等。

比如这样一个SQL语句就指定了拼接的字符串要按照'&'分割,并且要对age去重后按照正序返回:select name,GROUP_CONCAT(DISTINCT age order by age asc SEPARATOR '&') from t1 GROUP BY name;

image.png

线上遇到的问题

GROUP_CONCAT实际上还有一个允许返回的最大结果长度的参数:group_concat_max_len,且默认值为1024字节,也就是说如果拼接后的字符串超过这个长度则会被截断。

执行这个命令,表示将最大结果长度修改为1个字节:SET SESSION group_concat_max_len = 1;

此时再查询时,结果就被截断返回了: image.png

这实际上是个非常严重的问题,拼接后的字符串最后一位是1,客户端并不知情,会直接把1也当做目标结果来处理。

我们在实际业务应用中就由于没有考虑到这个问题,最终导致线上运营出现故障。

解决方式

解决方式自然很简单,要么调大group_concat_max_len参数值,要么客户端控制好一次性拼接的大小。

不过,需要注意的是group_concat_max_len尽管可以设置的很高,但最终其最大返回长度还受max_allowed_packet参数限制,其默认值为4M

额外扩展

类似的拼接函数,还有CONCAT_WS(separator,str1,str2,...)CONCAT(str1,str2,...)JSON_ARRAYAGG(col_or_expr)JSON_OBJECTAGG(key, value),在特定场景中,都是比较好用的拼接函数。

CONCAT_WS(separator,str1,str2,...)演示:

mysql> SELECT CONCAT_WS(',','First name','Second name','Last Name'); 
-> 'First name,Second name,Last Name' 
mysql> SELECT CONCAT_WS(',','First name',NULL,'Last Name'); 
-> 'First name,Last Name'

CONCAT(str1,str2,...)演示:

mysql> SELECT CONCAT('My', 'S', 'QL'); 
-> 'MySQL' 
mysql> SELECT CONCAT('My', NULL, 'QL'); 
-> NULL 
mysql> SELECT CONCAT(14.3); 
-> '14.3'

JSON_ARRAYAGG(col_or_expr)演示:

mysql> SELECT o_id, attribute, value FROM t3; 
+------+-----------+-------+ 
| o_id | attribute | value | 
+------+-----------+-------+ 
| 2 | color | red | | 2 | fabric | silk | 
| 3 | color | green | | 3 | shape | square| 
+------+-----------+-------+ 
4 rows in set (0.00 sec) 

mysql> SELECT o_id, JSON_ARRAYAGG(attribute) AS attributes 
-> FROM t3 GROUP BY o_id; 
+------+---------------------+ 
| o_id | attributes | 
+------+---------------------+ 
| 2 | ["color", "fabric"] | 
| 3 | ["color", "shape"] | 
+------+---------------------+ 
2 rows in set (0.00 sec)

JSON_OBJECTAGG(key, value)演示:

mysql> SELECT o_id, attribute, value FROM t3; 
+------+-----------+-------+ 
| o_id | attribute | value | 
+------+-----------+-------+ 
| 2 | color | red | | 2 | fabric | silk | 
| 3 | color | green | | 3 | shape | square| 
+------+-----------+-------+ 
4 rows in set (0.00 sec) 

mysql> SELECT o_id, JSON_OBJECTAGG(attribute, value) 
-> FROM t3 GROUP BY o_id; 
+------+---------------------------------------+ 
| o_id | JSON_OBJECTAGG(attribute, value) 
| +------+---------------------------------------+ 
| 2 | {"color": "red", "fabric": "silk"} | 
| 3 | {"color": "green", "shape": "square"} 
| +------+---------------------------------------+ 
2 rows in set (0.00 sec)

需要注意,因为JSON类型不允许有重复键出现,所以,如果attribute值重复,则会按照最后一个attribute值返回。