pupuk / mysql

Questions & Thinking

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

SQL_MODE 使用 MySQL 的 SQL_MODE 有哪些坑,你知道么?[转载学习]

pupuk opened this issue · comments

补充心得:

查看sql_mode的值:
SELECT @@sql_mode;

MySQL5.7的默认值:
ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION

MySQL8.0的默认值:
ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

差异在NO_AUTO_CREATE_USER

配置

还是跟mysql的大多数配置一样,有3种:

  • 配置文件: /etc/my.cnf
  • 全局会话:SET GLOBAL sql_mode = '你想要设置的值';
  • 全局会话:SET SESSION sql_mode = '你想要设置的值';
    MySQL8.0还支持,会话配置持久化到文件 MySQL 8.0的一些新特性

使用

  • sql_mode随着mysql新版本的发布,呈现越来越严格的趋势;
  • 老项目建议不要动,新项目逐渐在devtestprod环境使用mysql新的sql_mode;
  • 建议给予配置文件的配置,或者全局会话的配置;
  • 具体还是可以灵活,毕竟msyql提供了基于单个会话的配置;

特别说明

ONLY_FULL_GROUP_BY 这个属性,是很多包括我们自己从5.6切换到5.7以后,程序不适应的一个。这个配置要求,select语句中查询出来的列必须是明确的(其他语句也是一样)。
以SQL语句select columes from table group by list为例:columns必须是聚集函数或者在group by后的表达式list中,并且list中必须包含主键,否则也会报错。

insert、update、delete语句都会报错(但不影响SQL语句的执行),因为这三种语句执行之前也会执行查询操作。
以主键为id的表为例:
SELECT count(1) FROM customer GROUP BY name;该SQL执行成功,因为count是聚集函数;
SELECT * FROM customer GROUP BY name;该SQL执行失败,因为*中包含主键id,而group by后的表达式中并没有包含id
SELECT name FROM customer GROUP BY name;该SQL执行成功,因为name包含在group by后的表达式中
SELECT name, contact FROM customer GROUP BY name;该SQL执行失败,因为contact没有包含在group by后的表达式中

其实原因也很简单:
比如:SELECT * FROM customer GROUP BY name; 如果name='zhangsan'的记录有5条,但是这5条记录的sex,age却各不一样。
按理说这句mysql只会取出一条结果,那么给哪条结果合适呢?第一条?任意一条?好像都不太合适,所以MySQL5.7才会有
ONLY_FULL_GROUP_BY 这个属性,让你把用select的出的结果具有唯一性。
如果不开启该MODE,则允许SELECT列表中出现任意列,但这些列的值并不是确定的,官方文档中也提到了这一点。

If ONLY_FULL_GROUP_BY is disabled, a MySQL extension to the standard SQL use of GROUP BY permits the select list, HAVING condition, or ORDER BY list to refer to nonaggregated columns even if the columns are not functionally dependent on GROUP BY columns. This causes MySQL to accept the preceding query.
In this case, the server is free to choose any value from each group, so unless they are the same, the values chosen are nondeterministic, which is probably not what you want.
Furthermore, the selection of values from each group cannot be influenced by adding an ORDER BY clause. Result set sorting occurs after values have been chosen, and ORDER BY does not affect which value within each group the server chooses.
Disabling ONLY_FULL_GROUP_BY is useful primarily when you know that, due to some property of the data, all values in each nonaggregated column not named in the GROUP BY are the same for each group.

但是我们有时候就是需要其中任意一条数据,怎么办呢?

比如说,有这样的一个场景:
goods表,
goods_id, color_id, goods_name,goods_des 这几个字段
我们有一个商品列表,不希望列出所有的商品,想让颜色相同的商品,归成一个。不开启ONLY_FULL_GROUP_BY ,我们可以使用:
SELECT * FROM goods GROUP BY color_id;
若开启ONLY_FULL_GROUP_BY,这个语句会报错;

解决:
可以使用MySQL提供的函数ANY_VALUE
SELECT color_id,ANY_VALUE(goods_id), ANY_VALUE(goods_name), (goods_des ) FROM goods GROUP BY color_id;
不过如果这个表有很多字段,我们也要去很多,那需要很多ANY_VALUE也是个麻烦事。。

网上很多文章说,去改sql_mode, 把ONLY_FULL_GROUP_BY 这个属性删掉。。。这个只是说一个快速充数的临时解决方案吧。毕竟其他数据库,如SQLServer、Oracled也不支持select target list中出现语义不明确的列,这样的语句在这些数据库中是会被报错的,所以从MySQL 5.7版本开始修正了这个语义,逐渐向严格明确的方向看齐。

遇到的问题

有一种需求,是取每个分组最新的一条数据,或者取每个分组随机一条数据
如果用上面any_value的话,如果表有三十多个字段,要写三十多个any_value()函数,已经很恼火了,通常还需要any_value(col_a) AS col_a,这样的语句显得又臭又长,一点也不香。

有的人可能都想,关掉ONLY_FULL_GROUP_BY,来一个SELECT * FROM table_name GROUP BY col_xxx; 放弃治疗。

但是使用ONLY_FULL_GROUP_BY的确是趋势,也是向SQLServer、Oracled看齐,使用明确的语义来编写SQL也是避免出一些难以理解和排查的bug。

以下记录一种解决办法;举例说明;
有如下一个商品表:

CREATE TABLE `product` (
  `product_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增id',
  `product_code` varchar(5) NOT NULL DEFAULT '' COMMENT '算力名称CODE(BTC, IPFS)',
  `product_type` varchar(255) NOT NULL DEFAULT '' COMMENT '矿机类型(machine:台 ;cloud:份)',
  PRIMARY KEY (`product_id`),
  KEY `product_type` (`product_type`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COMMENT='商品表';

每组随机取一条完整的数据;
SELECT product_type, any_value(product_id) AS product_id FROM product GROUP BY product_type;
image

拿到product_id以后,再去查询;
SELECT * FROM product WHERE product_id IN (1,56,55);

如果product_type字段上有索引,且innodb_buffer_pool足够大的话,则第一条语句,基本上在内存中就能获取any_value(product_id), 第二条语句的IN查询也是走聚簇索引,非常快。