<返回更多

MySQL中的sql_mode的解读

2021-06-08  今日头条  程序猿集锦
加入收藏

前言

sql_mode是MySQL中比较重要的一个全局参数,这个参数的值对MySQL整体的运行有着极为重要的意义。但是一般情况下,大家并不怎么的去修改它的值,都是使用默认值。但是当我们在数据库之间迁移的时候,很有可能遇到这个参数引起的坑。

下面我们把这个参数的具体功能和参数值逐一分析一下。

sql_mode参数

如何查看sql_mode的值

查看sql_mode的值很简单,使用如下show variables like 'sql_mode'命令即可查看MySQL数据库的sql_mode的值。

mysql> mysql> show variables like 'sql_mode'G
*************************** 1. row ***************************
Variable_name: sql_mode
        Value: 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
1 row in set (0.01 sec)

mysql>

如何设置sql_mode的值

sql_mode可以配置很多值,多个值之间使用逗号分隔即可。如下:

set session sql_mode="a,b,c,d";
set global sql_mode="a,b,c,d";

需要注意的是:sql_mode的值,可以分为session级别和global两个级别,session级别的修改,只针对当前登录的session有效,其他session登录后,还是使用原先的global级别的值。global级别的修改,除了针对当前的session有效之外,对以后新创建的任何其他session都是有效的。

sql_mode参数列表

参数sql_mode的值,可以设置为哪些值呢?下面我们针对每一个参数值看一下。

备注:下面所有的实验,对sql_mode的修改,都是基于session级别的修改。

ONLY_FULL_GROUP_BY

sql_mode配置上这个参数之后,表示SQL语句中涉及到的聚合函数的使用的时候,需要在group_by子句中包含所有的分组列,不能漏掉任何一个分组列。言外之意就是在所有的select字段中,除了聚合函数之外的所有字段,都应该出现在group by子句中。

示例演示。创建一个表T,结构和初始化数据如下所示,我们接下来的操作针对这个表来做实验。

mysql> desc t;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(11)     | NO   | PRI | NULL    | auto_increment |
| a     | varchar(16) | YES  |     | NULL    |                |
| b     | varchar(16) | YES  |     | NULL    |                |
| c     | int(11)     | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

mysql> select * from t;
+----+------+------+------+
| id | a    | b    | c    |
+----+------+------+------+
|  1 | x1   | y1   |   10 |
|  2 | x1   | y1   |    5 |
|  3 | x1   | y3   |    2 |
|  4 | x2   | y2   |   20 |
|  5 | x2   | y3   |   20 |
|  6 | x2   | y3   |    6 |
+----+------+------+------+
6 rows in set (0.00 sec)

mysql>

我们把先当前的sql_mode的值设置为空,然后再把sql_mode的值设置为ONLY_FULL_GROUP_BY。针对这两种设置方式,我们来测试同一个group by语句的执行效果是怎样的。

mysql> set session sql_mode = '';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> show session variables like 'sql_mode';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| sql_mode      |       |
+---------------+-------+
1 row in set (0.00 sec)

mysql>

接下来我我们执行下面的group_by语句,看下结果是什么样子的。

mysql> select * from t;
+----+------+------+------+
| id | a    | b    | c    |
+----+------+------+------+
|  1 | x1   | y1   |   10 |
|  2 | x1   | y1   |    5 |
|  3 | x1   | y3   |    2 |
|  4 | x2   | y2   |   20 |
|  5 | x2   | y3   |   20 |
|  6 | x2   | y3   |    6 |
+----+------+------+------+
6 rows in set (0.00 sec)

mysql> select a, b, sum(c) from t group by a, b; # 可以执行成功
+------+------+--------+
| a    | b    | sum(c) |
+------+------+--------+
| x1   | y1   |     15 |
| x1   | y3   |      2 |
| x2   | y2   |     20 |
| x2   | y3   |     26 |
+------+------+--------+
4 rows in set (0.01 sec)

# 下面在缺少一个group_by字段的时候,也可以执行成功,只不过结果和上面有些区别。
mysql> select a, b, sum(c) from t group by a;
+------+------+--------+
| a    | b    | sum(c) |
+------+------+--------+
| x1   | y1   |     17 |
| x2   | y2   |     46 |
+------+------+--------+
2 rows in set (0.00 sec)

mysql>

下面我们把sql_mode的参数修改为ONLY_FULL_GROUP_BY,再分别执行一下上面的两个group by语句。

mysql> set session sql_mode='ONLY_FULL_GROUP_BY';
Query OK, 0 rows affected (0.00 sec)

mysql> show session variables like 'sql_mode';
+---------------+--------------------+
| Variable_name | Value              |
+---------------+--------------------+
| sql_mode      | ONLY_FULL_GROUP_BY |
+---------------+--------------------+
1 row in set (0.00 sec)

mysql>

下面看执行group by语句的效果:

mysql> select * from t;
+----+------+------+------+
| id | a    | b    | c    |
+----+------+------+------+
|  1 | x1   | y1   |   10 |
|  2 | x1   | y1   |    5 |
|  3 | x1   | y3   |    2 |
|  4 | x2   | y2   |   20 |
|  5 | x2   | y3   |   20 |
|  6 | x2   | y3   |    6 |
+----+------+------+------+
6 rows in set (0.00 sec)

mysql> select a, b, sum(c) from t group by a, b; # 可以执行成功
+------+------+--------+
| a    | b    | sum(c) |
+------+------+--------+
| x1   | y1   |     15 |
| x1   | y3   |      2 |
| x2   | y2   |     20 |
| x2   | y3   |     26 |
+------+------+--------+
4 rows in set (0.00 sec)

mysql> select a, b, sum(c) from t group by a; # 执行失败,提示sql_mode的值包含only_full_group_by。
ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'db1.t.b' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
mysql>

通过以上两种参数的设置,相信你对sql_mode的参数ONLY_FULL_GROUP_BY的作用应该有一个深刻的体会了。建议将sql_mode的参数值设置为包含ONLY_FULL_GROUP_BY选项,因为这样更加的符合SQL规范标准,在以后做跨类型数据库迁移的时候,也方便把当前的SQL语句,直接迁移到其他类型的数据库中去,例如:Oracle、SQLserver等数据库。

ANSI_QUOTES

这个参数的含义是在SQL语句中,对于字符串需要使用单引号来包裹起来,而不是既支持单引号又支持双引号。下面我们来看实例。

首选,我们的sql_mode是不包含ANSI_QUOTES参数的,如下所示:

mysql> show session variables like 'sql_mode';
+---------------+--------------------+
| Variable_name | Value              |
+---------------+--------------------+
| sql_mode      | ONLY_FULL_GROUP_BY |
+---------------+--------------------+
1 row in set (0.00 sec)

mysql>

下面看下我们的两个查询字符串的SQL语句:

mysql> select * from t where a='x1' and b='y3'; # 字符串通过单引号包裹可以执行
+----+------+------+------+
| id | a    | b    | c    |
+----+------+------+------+
|  3 | x1   | y3   |    2 |
+----+------+------+------+
1 row in set (0.00 sec)

mysql> select * from t where a="x1" and b="y3"; # 字符串通过双引号包裹也可以执行
+----+------+------+------+
| id | a    | b    | c    |
+----+------+------+------+
|  3 | x1   | y3   |    2 |
+----+------+------+------+
1 row in set (0.00 sec)

mysql>

通过上面的两个SQL语句,我们发现:当sql_mode的参数中,不包含ANSI_QUOTES参数的时候,在SQL语句中,字符串既可以用单引号包裹也可以用双引号包裹。下面我们在看下在sql_mode参数中,配置上ANSI_QUOTES参数后,看下SQL语句的效果是什么样子的。

mysql> set session sql_mode='ONLY_FULL_GROUP_BY,ANSI_QUOTES';
Query OK, 0 rows affected (0.00 sec)

mysql> show session variables like 'sql_mode';
+---------------+--------------------------------+
| Variable_name | Value                          |
+---------------+--------------------------------+
| sql_mode      | ANSI_QUOTES,ONLY_FULL_GROUP_BY |
+---------------+--------------------------------+
1 row in set (0.00 sec)

mysql>

在sql_mode的参数包含了ANSI_QUOTES值之后,我们执行如下的SQL语句,观察执行的结果是怎么样的:

mysql> select * from t where a='x1' and b='y3'; # 字符串通过单引号包裹可以执行
+----+------+------+------+
| id | a    | b    | c    |
+----+------+------+------+
|  3 | x1   | y3   |    2 |
+----+------+------+------+
1 row in set (0.00 sec)

mysql> select * from t where a="x1" and b="y3";  # 字符串通过双引号包裹,执行失败
ERROR 1054 (42S22): Unknown column 'x1' in 'where clause'
mysql>

通过上面的SQL执行结果,我们可以发现:在sql_mode参数中,包含了ANSI_QUOTES值之后,在SQL语句中所有的字符串,都只能够通过单引号来包裹,不再支持双引号包裹。

建议sql_mode参数中包含ANSI_QUOTES的设置,这样使得我们的开发中写的SQL更加的符合SQL标准规范,便于以后数据库的迁移。

PIPES_AS_CONCAT

当sql_mode的值中包含PIPES_AS_CONCAT的时候,表示在SQL语句中,可以使用||来表示字符串的连接,这个字符不会被当做或运算符,否则MySQL中只能使用concat函数来连接字符串,||这个字符会被当做或运算符。

下面我们针对sql_mode中包含这个参数值和不包含这个参数值的两种情况分别做一下测试。在测试之前,我们先看一下当前的sql_mode的值是什么样的,如下:

mysql> show session variables like 'sql_mode';
+---------------+--------------------------------+
| Variable_name | Value                          |
+---------------+--------------------------------+
| sql_mode      | ANSI_QUOTES,ONLY_FULL_GROUP_BY |
+---------------+--------------------------------+
1 row in set (0.04 sec)

mysql>

当前的sql_mode中,没有包含参数值PIPES_AS_CONCAT,下面我们看一下字符串连接的时候,我们使用||来连接,效果是什么样子的,如下:

mysql> select 'hello' || 'mysql';
+--------------------+
| 'hello' || 'mysql' |
+--------------------+
|                  0 |
+--------------------+
1 row in set, 2 warnings (0.01 sec)

mysql> select 'hello' || 123;
+----------------+
| 'hello' || 123 |
+----------------+
|              1 |
+----------------+
1 row in set, 1 warning (0.00 sec)

mysql> select concat('hello','mysql');
+-------------------------+
| concat('hello','mysql') |
+-------------------------+
| hellomysql              |
+-------------------------+
1 row in set (0.01 sec)

mysql>

通过上面的实验我们发现,当sql_mode中不包含PIPES_AS_CONCAT值的时候,字符串的连接不能使用双竖线,只能使用concat函数来连接。下面我们来看一下当sql_mode中包含PIPES_AS_CONCAT值的时候,效果是什么样子的。

在实验之前,我们先设置一下session级别的sql_mode的值,设置如下:

mysql> set session sql_mode='ANSI_QUOTES,ONLY_FULL_GROUP_BY,PIPES_AS_CONCAT';
Query OK, 0 rows affected (0.00 sec)

mysql> show session variables like 'sql_mode';
+---------------+------------------------------------------------+
| Variable_name | Value                                          |
+---------------+------------------------------------------------+
| sql_mode      | PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY |
+---------------+------------------------------------------------+
1 row in set (0.00 sec)

mysql>

实验结果如下:

mysql> select 'hello' || 'mysql';
+--------------------+
| 'hello' || 'mysql' |
+--------------------+
| hellomysql         |
+--------------------+
1 row in set (0.00 sec)

mysql> select 'hello' || 123;
+----------------+
| 'hello' || 123 |
+----------------+
| hello123       |
+----------------+
1 row in set (0.00 sec)

mysql> select concat('hello','mysql');
+-------------------------+
| concat('hello','mysql') |
+-------------------------+
| hellomysql              |
+-------------------------+
1 row in set (0.00 sec)

mysql>

通过上面的实验,我们可以发现,当sql_mode的参数值包含PIPES_AS_CONCAT值的时候,SQL语句中的字符串的连接,除了可以使用concat函数之外,还是可以使用双竖线||来连接。

在平时的设置中建议sql_mode的值包含PIPES_AS_CONCAT,因为这样更加符合SQL标准,便于在以后进行数据库的异构迁移。

NO_AUTO_CREATE_USER

这个参数值的作用是用来控制通过grant语句授权并创建用户的时候,是否可以创建密码为空的用户。对于create user语句创建用户是不受这个参数的控制的。下面我们来看一下具体的效果是什么样子的。

先查看当前的sql_mode的参数值是不包含NO_AUTO_CREATE_USER值的,如下所示:

mysql> show session variables like 'sql_mode';
+---------------+------------------------------------------------+
| Variable_name | Value                                          |
+---------------+------------------------------------------------+
| sql_mode      | PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY |
+---------------+------------------------------------------------+
1 row in set (0.00 sec)

mysql>

下面查看grant语句的执行效果:

mysql> select user,host from mysql.user; # 当前没有user1和user2两个用户。
+---------------+-----------+
| user          | host      |
+---------------+-----------+
| mysql.session | localhost |
| mysql.sys     | localhost |
| root          | localhost |
+---------------+-----------+
3 rows in set (0.00 sec)

mysql> grant select on test.* to user1; # 创建user1的时候,不指定密码,创建成功。
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> grant select on test.* to user2 identified by ''; # 创建user2的时候,指定为空密码,创建也成功了。
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> select user,host from mysql.user; # 查询user1和user2两个用户创建成功后的结果。
+---------------+-----------+
| user          | host      |
+---------------+-----------+
| user1         | %         |
| user2         | %         |
| mysql.session | localhost |
| mysql.sys     | localhost |
| root          | localhost |
+---------------+-----------+
5 rows in set (0.00 sec)

mysql>

通过上面的grant语句的实验结果,我们发现:当sql_mode中不包含NO_AUTO_CREATE_USER值的时候,使用grant语句创建并授权用户的时候,是可以设置新用户的密码为空或不指定它的密码的。下面我们在设置一下sql_mode参数的值,把NO_AUTO_CREATE_USER的值配置上,再来验证一下grant语句的效果。

mysql> set session sql_mode='PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,NO_AUTO_CREATE_USER';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> show session variables like 'sql_mode';
+---------------+--------------------------------------------------------------------+
| Variable_name | Value                                                              |
+---------------+--------------------------------------------------------------------+
| sql_mode      | PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,NO_AUTO_CREATE_USER |
+---------------+--------------------------------------------------------------------+
1 row in set (0.01 sec)

mysql>

在sql_mode的参数值中包含NO_AUTO_CREATE_USER之后,在使用grant语句授权并且创建用户的时候,需要指定用户的密码不为空才可以创建成功,如下所示:

mysql> grant select on *.* to user3; # 创建user3失败,因为它的密码没有指定
ERROR 1133 (42000): Can't find any matching row in the user table
mysql> grant select on *.* to user4 identified by ''; # 创建user4失败,因为它的密码指定的为空字符串
ERROR 1133 (42000): Can't find any matching row in the user table
mysql> grant select on *.* to user5 identified by 'user5'; # 创建user5成功,因为为它指定了非空的密码。
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> select user,host from mysql.user; # 查看用户列表,可以发先user5创建成了,user3和user4并没有成功创建。
+---------------+-----------+
| user          | host      |
+---------------+-----------+
| user1         | %         |
| user2         | %         |
| user5         | %         |
| mysql.session | localhost |
| mysql.sys     | localhost |
| root          | localhost |
+---------------+-----------+
6 rows in set (0.00 sec)

mysql>

通过上面的语句实验,可以知道:在sql_mode包含NO_AUTO_CREATE_USER值之后,在通过grant语句创建用户的时候,如果不指定待创建的用户的密码或者指定为空字符串密码,创建是不会成功了。需要指定对应的非空密码才可以创建成功。

建议把sql_mode的值配置上NO_AUT0_CREATE_USER,这样可以避免创建用户的时候不设置用户密码,导致账号不安全,避免MySQL数据库遭受到恶意攻击。

STRICT_TRANS_TABLES

在sql_mode的参数列表中,如果包含STRICT_TRANS_TABLES值,表示当前的MySQL实例是运行在严格模式下。如果没有STRICT_TRANS_TABLES这个值,则表示MySQL实例是运行在非严格模式下。

下面我们区分一下严格模式和非严格模式的区别。

下面我们就这对严格模式和非严格模式分别做一下实验。我们知道MySQL中常用的两种存储引擎:InnoDB、MyISAM,其中前者支持事务,后者不支持事务。我们就在两种模式下面分别针对这两种存储引擎的表进行实验。

严格模式下的实验

首先我们将sql_mode设置为严格模式,如下所示是包含STRICT_TRANS_TABLES值的,表示MySQL是运行在严格模式下。

mysql> set session sql_mode='PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,NO_AUTO_CREATE_USER,STRICT_TRANS_TABLES';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> show session variables like 'sql_mode'G
*************************** 1. row ***************************
Variable_name: sql_mode
        Value: PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER
1 row in set (0.00 sec)

mysql>

我们再创建两个表,一个是InnoDB存储引擎的表tab_innodb,一个是MyISAM存储引擎表tab_myisam。如下所示:

mysql> show create table tab_innodbG
*************************** 1. row ***************************
       Table: tab_innodb
Create Table: CREATE TABLE "tab_innodb" (
  "id" int(11) NOT NULL,
  "a" int(11) NOT NULL,
  "b" int(11) DEFAULT NULL,
  "c" varchar(16) NOT NULL,
  PRIMARY KEY ("id")
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

mysql> show create table tab_myisamG
*************************** 1. row ***************************
       Table: tab_myisam
Create Table: CREATE TABLE "tab_myisam" (
  "id" int(11) NOT NULL,
  "a" int(11) NOT NULL,
  "b" int(11) DEFAULT NULL,
  "c" varchar(16) NOT NULL,
  PRIMARY KEY ("id")
) ENGINE=MyISAM DEFAULT CHARSET=latin1
1 row in set (0.01 sec)

mysql>

下面我们分别向两个存储引擎的表中批量的插入四条数据,其中第3条数据是不符合数据要求的,其他1、2、4这三条数据是复合要求的。我们看下在这两个表中插入后的效果是什么样子的。

mysql> insert into tab_innodb(id,a,b,c) values(1,1,1,'aaaa'),(2,2,2,'bbbb'),(3,null,3,'cccc'),(4,4,4,'dddd'); # 插入失败,因为第三行数据不符合数据要求。
ERROR 1048 (23000): Column 'a' cannot be null
mysql> select * from tab_innodb; # 查询表中的数据发现任何数据都没有插入成功,事务回滚了。
Empty set (0.00 sec)

mysql> insert into tab_myisam(id,a,b,c) values(1,1,1,'aaaa'),(2,2,2,'bbbb'),(3,null,3,'cccc'),(4,4,4,'dddd'); # 插入成功,但是有一条Warning信息。
Query OK, 4 rows affected, 1 warning (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 1

mysql> show warnings; # 查看Warning信息是什么
+---------+------+---------------------------+
| Level   | Code | Message                   |
+---------+------+---------------------------+
| Warning | 1048 | Column 'a' cannot be null |
+---------+------+---------------------------+
1 row in set (0.00 sec)

mysql> select * from tab_myisam; # 查看表中的数据,发现四条数据全部插入成功,只不过第三条数据的a列的值我们设置的为null,插入表中后,被转换成了0。
+----+---+------+------+
| id | a | b    | c    |
+----+---+------+------+
|  1 | 1 |    1 | aaaa |
|  2 | 2 |    2 | bbbb |
|  3 | 0 |    3 | cccc |
|  4 | 4 |    4 | dddd |
+----+---+------+------+
4 rows in set (0.00 sec)

mysql>

通过以上实验,我们可以看出,sql_mode在严格模式下,对数据的验证结果是复合我们前面总结的规律的。下面我们看一下非严格模式下面的实验。

非严格模式下的实验

先把sql_mode设置为非严格模式,如下所示:

mysql> set session sql_mode='PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,NO_AUTO_CREATE_USER';
Query OK, 0 rows affected (0.00 sec)

mysql> show session variables like 'sql_mode'G
*************************** 1. row ***************************
Variable_name: sql_mode
        Value: PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,NO_AUTO_CREATE_USER
1 row in set (0.01 sec)

mysql>

下面我们向前面使用到的两个表进行同样的插入数据的测试,注意在测试之前,先清空两张表中的数据。

mysql> truncate table tab_myisam; # 实验之前,先清空表中的数据。
Query OK, 0 rows affected (0.01 sec)

mysql> truncate table tab_innodb; # 实验之前,先清空表中的数据。
Query OK, 0 rows affected (0.01 sec)

mysql> insert into tab_innodb(id,a,b,c) values(1,1,1,'aaaa'),(2,2,2,'bbbb'),(3,null,3,'cccc'),(4,4,4,'dddd');  # 向innodb存储引擎的表中批量插入数据,成功,但是有警告信息。
Query OK, 4 rows affected, 1 warning (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 1

mysql> show warnings;  # 查看警告信息
+---------+------+---------------------------+
| Level   | Code | Message                   |
+---------+------+---------------------------+
| Warning | 1048 | Column 'a' cannot be null |
+---------+------+---------------------------+
1 row in set (0.00 sec)

mysql> select * from tab_innodb; # 查看最后表中的数据,有4一条数据,但是第三条数据的a列的值,我们设置为null,但是被转换为0插入表中,这就造成了脏数据。我们设置为null,结果给我们转换为0存进去了。
+----+---+------+------+
| id | a | b    | c    |
+----+---+------+------+
|  1 | 1 |    1 | aaaa |
|  2 | 2 |    2 | bbbb |
|  3 | 0 |    3 | cccc |
|  4 | 4 |    4 | dddd |
+----+---+------+------+
4 rows in set (0.00 sec)

mysql> insert into tab_myisam(id,a,b,c) values(1,1,1,'aaaa'),(2,2,2,'bbbb'),(3,null,3,'cccc'),(4,4,4,'dddd'); # 向MyISAM存储引擎的表中插入数据,成功,但是也有警告信息。
Query OK, 4 rows affected, 1 warning (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 1

mysql> show warnings; # 查看警告信息
+---------+------+---------------------------+
| Level   | Code | Message                   |
+---------+------+---------------------------+
| Warning | 1048 | Column 'a' cannot be null |
+---------+------+---------------------------+
1 row in set (0.00 sec)

mysql> select * from tab_myisam; # 查看表中的数据,全部成功,但是第三条数据的a列的值是0,并不是我们设置为null。
+----+---+------+------+
| id | a | b    | c    |
+----+---+------+------+
|  1 | 1 |    1 | aaaa |
|  2 | 2 |    2 | bbbb |
|  3 | 0 |    3 | cccc |
|  4 | 4 |    4 | dddd |
+----+---+------+------+
4 rows in set (0.00 sec)

mysql>

通过上面的实验,我们发现,sql_mode配置为非严格模式的时候,实验结果也是符合我们总结的规律的。

**总结:**建议将sql_mode配置为严格模式,因为在严格模式下可以避免脏数据的插入和更新,并且InnoDB存储引擎目前是大家经常使用的存储引擎,在这种支持事务的存储引擎下面,严格模式更能保证脏数据的产生。

STRICT_ALL_TABLES

对于InnoDB存储引擎,这个参数的作用和STRICT_TRANS_TABLES作用一致。
对于MyISAM存储引擎,当插入不是第一行报错时,会将报错,但是之前的数据保存到数据库中,同时终止之后的插入操作,如果不配置这个参数,不会报错,发生隐式转换插入库中,同时后面的数据也会继续插入到库中。下面我们来具体的实验步骤,看下具体的效果。

我们这个实验的时候只配置STRICT_ALL_TABLES,不配置前面的STRICT_TRANS_TABLES参数。单独观察这个参数的效果。下面为sql_mode的配置过程:

mysql> set session sql_mode='PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,NO_AUTO_VALUE_ON_ZERO,NO_AUTO_CREATE_USER,STRICT_ALL_TABLES';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> show session variables like 'sql_mode'G
*************************** 1. row ***************************
Variable_name: sql_mode
        Value: PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,NO_AUTO_VALUE_ON_ZERO,STRICT_ALL_TABLES,NO_AUTO_CREATE_USER
1 row in set (0.00 sec)

mysql>

向两种存储类型的表中批量插入多条数据,看下效果是怎么样的。注意:在插入之前,先清空之前的测试数据。

mysql> truncate table tab_innodb;
Query OK, 0 rows affected (0.01 sec)

mysql> truncate table tab_myisam;
Query OK, 0 rows affected (0.01 sec)

mysql> insert into tab_innodb(id,a,b,c) values(1,1,1,'aaaa'),(2,2,2,'bbbb'),(3,null,3,'cccc'),(4,4,4,'dddd'); # 支持事务的innodb存储引擎的表,插入失败,通过下面的查询结果可以看出,一条都没插入成功。
ERROR 1048 (23000): Column 'a' cannot be null
mysql> select * from tab_innodb;
Empty set (0.00 sec)

mysql> insert into tab_myisam(id,a,b,c) values(1,1,1,'aaaa'),(2,2,2,'bbbb'),(3,null,3,'cccc'),(4,4,4,'dddd'); # 不支持事务的myisam存储引擎的表,插入失败,但是通过下面的查询结果可以看出,前2条成功了,第4条没有成功。
ERROR 1048 (23000): Column 'a' cannot be null
mysql> select * from tab_myisam;
+----+---+------+------+
| id | a | b    | c    |
+----+---+------+------+
|  1 | 1 |    1 | aaaa |
|  2 | 2 |    2 | bbbb |
+----+---+------+------+
2 rows in set (0.00 sec)

mysql>

这就是STRICT_ALL_TABLES参数的作用。在配置上这个参数之后,对于支持事务的表,插入数据的过程中,如果出现非法的数据,那么整个插入全部失败,会回滚前面插入成功的数据,后面的插入操作也不会执行了,这个效果和参数STRICT_TRANS_TABLES的效果是一样的;但是对于不支持事务的表,在插入的过程中,如果有非法数据出现,也会出现错误信息,但是前面已经插入的数据不会回滚,后续的插入也不会执行。但是对于参数STRICT_TRANS_TABLES参数而言,非法的数据行会做隐式转换插入的表中,并且后续的插入数据参照仍然会继续,最后所有的数据行都会插入到表中。

下面看下没有STRCIT_ALL_TABLES参数的效果。

mysql> set session sql_mode='PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,NO_AUTO_VALUE_ON_ZERO,NO_AUTO_CREATE_USER';
Query OK, 0 rows affected (0.00 sec)

mysql> show session variables like 'sql_mode'G
*************************** 1. row ***************************
Variable_name: sql_mode
        Value: PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,NO_AUTO_VALUE_ON_ZERO,NO_AUTO_CREATE_USER
1 row in set (0.01 sec)

mysql>

插入结果验证:

mysql> truncate table tab_innodb; # 插入前,先清空。
Query OK, 0 rows affected (0.01 sec)

mysql> truncate table tab_myisam; # 插入前,先清空。
Query OK, 0 rows affected (0.00 sec)

mysql> insert into tab_innodb(id,a,b,c) values(1,1,1,'aaaa'),(2,2,2,'bbbb'),(3,null,3,'cccc'),(4,4,4,'dddd'); # 插入成功,但是你有警告信息。
Query OK, 4 rows affected, 1 warning (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 1

mysql> show warnings; # 查看警告信息
+---------+------+---------------------------+
| Level   | Code | Message                   |
+---------+------+---------------------------+
| Warning | 1048 | Column 'a' cannot be null |
+---------+------+---------------------------+
1 row in set (0.00 sec)

mysql> select * from tab_innodb; # 查看支持事务的存储引擎表的数据,都插入了数据,只是不符合数据要求的第3行的a列发生的数据的null到0的隐式转换。
+----+---+------+------+
| id | a | b    | c    |
+----+---+------+------+
|  1 | 1 |    1 | aaaa |
|  2 | 2 |    2 | bbbb |
|  3 | 0 |    3 | cccc |
|  4 | 4 |    4 | dddd |
+----+---+------+------+
4 rows in set (0.00 sec)

mysql> insert into tab_myisam(id,a,b,c) values(1,1,1,'aaaa'),(2,2,2,'bbbb'),(3,null,3,'cccc'),(4,4,4,'dddd');  # 插入成功,但是你有警告信息。
Query OK, 4 rows affected, 1 warning (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 1

mysql> show warnings; # 查看警告信息
+---------+------+---------------------------+
| Level   | Code | Message                   |
+---------+------+---------------------------+
| Warning | 1048 | Column 'a' cannot be null |
+---------+------+---------------------------+
1 row in set (0.00 sec)

mysql> select * from tab_myisam;# 查看不支持事务的存储引擎表的数据,都插入了数据,只是不符合数据要求的第3行的a列发生的数据的null到0的隐式转换。
+----+---+------+------+
| id | a | b    | c    |
+----+---+------+------+
|  1 | 1 |    1 | aaaa |
|  2 | 2 |    2 | bbbb |
|  3 | 0 |    3 | cccc |
|  4 | 4 |    4 | dddd |
+----+---+------+------+
4 rows in set (0.00 sec)

mysql>

通过上面的实验,我们发现在没有配置STRICT_ALL_TABLES参数的时候,不管是否支持事务的表,在插入数据的时候,遇到非法的数据行后,会发生隐式转换并且插入都是成功,后续的所有插入操作都会继续进行,之前插入的数据行也会保留。这个效果和没有配置参数STRICT_TRANS_TABLES的效果是一样的。

建议:在sql_mode中配置上这个参数,这样就可以避免在对于不支持事务存储引擎的表,做批量插入或修改时,遇到非法数据后不报错误,只有警告信息,而且错误警告信息后面的SQL操作也会继续执行。

如果配置了STRICT_ALL_TABLES就不用配置STRICT_TRANS_TABLES参数了,因为前者的功能包含了后者的功能,前者的功能在后者的基础上,对不支持事务的表的操作上,做了的批量插入或修改中断的操作,中断错误行后面的SQL操作。

ERROR_FOR_DIVISION_BY_ZERO

如果sql_mode的参数中不包含这个值,那么在我们写的SQL语句中,如果使用数字0作为分母除数,select语句正常执行返回一个null值,执行insert、update语句的时候,也可以正常执行,只不过结果并不是我们想要的结果,结果也是null值。

如果在sql_mode中设置了

ERROR_FOR_DIVISION_BY_ZERO,当我们在select语句中使用了0作为分母的时候,那么会抛出一个Warning警告信息。在执行insert、updateSQL语句的时候,使用0作为除数的时候,则会出现一个Error信息。

注意:sql_mode参数的值

ERROR_FOR_DIVISION_BY_ZERO生效的前提是在SQL语句的严格模式下才会生效,也就是在sql_mode参数中,配置了STRICT_TRANS_TABLES参数的前提下,

ERROR_FOR_DIVISION_BY_ZERO的功能才会生效。

下面我们来看一下具体的效果,先看下一现在的sql_mode的值,是不包含

ERROR_FOR_DIVISION_BY_ZERO,如下所示:

mysql> show session variables like 'sql_mode';
+---------------+--------------------------------------------------------------------+
| Variable_name | Value                                                              |
+---------------+--------------------------------------------------------------------+
| sql_mode      | PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,NO_AUTO_CREATE_USER |
+---------------+--------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql>

使用0作为分母的时候,select语句执行结果如下,查询语句可以正常执行,并不会出现任何警告信息。insert、update语句执行也没有任何问题。

mysql> select 4/0;
+------+
| 4/0  |
+------+
| NULL |
+------+
1 row in set (0.00 sec)

mysql> select 0/0;
+------+
| 0/0  |
+------+
| NULL |
+------+
1 row in set (0.00 sec)

mysql> desc t;
+-------+-------------+------+-----+-------------------+-----------------------------+
| Field | Type        | Null | Key | Default           | Extra                       |
+-------+-------------+------+-----+-------------------+-----------------------------+
| id    | int(11)     | NO   | PRI | NULL              | auto_increment              |
| a     | varchar(16) | YES  |     | NULL              |                             |
| b     | varchar(16) | YES  |     | NULL              |                             |
| x     | int(11)     | YES  |     | NULL              |                             |
| y     | int(11)     | YES  |     | NULL              |                             |
| z     | int(11)     | YES  |     | NULL              |                             |
| c     | timestamp   | YES  |     | NULL              |                             |
| d     | timestamp   | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------+-------------+------+-----+-------------------+-----------------------------+
8 rows in set (0.01 sec)

mysql> select * from t;
+----+------+------+------+------+------+---------------------+---------------------+
| id | a    | b    | x    | y    | z    | c                   | d                   |
+----+------+------+------+------+------+---------------------+---------------------+
|  1 | x1   | y1   |    8 |    0 |   11 | 2021-06-01 14:44:29 | 2021-06-01 17:57:36 |
|  2 | x1   | y1   |    8 |    0 |   11 | 2021-06-01 14:44:29 | 2021-06-01 17:57:36 |
|  3 | x1   | y3   |    8 |    0 |   11 | 2021-06-01 14:44:29 | 2021-06-01 17:57:36 |
|  4 | x2   | y2   |    8 |    0 |   11 | 2021-06-01 14:44:29 | 2021-06-01 17:57:36 |
|  5 | x2   | y3   |    8 |    0 |   11 | 2021-06-01 14:44:29 | 2021-06-01 17:57:36 |
|  6 | x2   | y3   |    8 |    0 |   11 | 2021-06-01 14:44:29 | 2021-06-01 17:57:36 |
+----+------+------+------+------+------+---------------------+---------------------+
6 rows in set (0.00 sec)

mysql> update t set z = x / y where id = 2; # update语句中,包含0作为除数的情况,已经可以执行成功,只是z列结果是null。
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> insert into t(a,b,x,y,z) values ('x3','y3',1/0,1,1); # insert语句中,也有0作为除数的请,也可以执行成功。只是x列的值为null。
Query OK, 1 row affected (0.00 sec)

mysql> select * from t;
+----+------+------+------+------+------+---------------------+---------------------+
| id | a    | b    | x    | y    | z    | c                   | d                   |
+----+------+------+------+------+------+---------------------+---------------------+
|  1 | x1   | y1   |    8 |    0 |   11 | 2021-06-01 14:44:29 | 2021-06-01 17:57:36 |
|  2 | x1   | y1   |    8 |    0 | NULL | 2021-06-01 14:44:29 | 2021-06-01 17:57:42 |
|  3 | x1   | y3   |    8 |    0 |   11 | 2021-06-01 14:44:29 | 2021-06-01 17:57:36 |
|  4 | x2   | y2   |    8 |    0 |   11 | 2021-06-01 14:44:29 | 2021-06-01 17:57:36 |
|  5 | x2   | y3   |    8 |    0 |   11 | 2021-06-01 14:44:29 | 2021-06-01 17:57:36 |
|  6 | x2   | y3   |    8 |    0 |   11 | 2021-06-01 14:44:29 | 2021-06-01 17:57:36 |
|  7 | x3   | y3   | NULL |    1 |    1 | NULL                | 2021-06-01 17:58:26 |
+----+------+------+------+------+------+---------------------+---------------------+
7 rows in set (0.00 sec)

mysql>

现在,我们把sql_mode的值,增加

ERROR_FOR_DIVISION_BY_ZERO,然后再看下是否还可以使用0作为分母。操作如下:

mysql> set session sql_mode='PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,NO_AUTO_CREATE_USER,ERROR_FOR_DIVISION_BY_ZERO';
Query OK, 0 rows affected, 1 warning (0.00 sec) # 这里设置sql_mode的参数后,出现了一个warning,下面看下具体警告内容

# 通过下面的warning信息,我们知道ERROR_FOR_DIVISION_BY_ZERO参数需要配合STRICT_TRANS_TABLES参数一起使用。
mysql> show warningsG
*************************** 1. row ***************************
  Level: Warning
   Code: 3135
Message: 'NO_ZERO_DATE', 'NO_ZERO_IN_DATE' and 'ERROR_FOR_DIVISION_BY_ZERO' sql modes should be used with strict mode. They will be merged with strict mode in a future release.
1 row in set (0.00 sec)

# 重新设置sql_mode的值,增加上STRICT_TRANS_TABLES的配置。
mysql> set session sql_mode='PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,NO_AUTO_CREATE_USER,ERROR_FOR_DIVISION_BY_ZERO,STRICT_TRANS_TABLES';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> show session variables like 'sql_mode'G
*************************** 1. row ***************************
Variable_name: sql_mode
        Value: PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER
1 row in set (0.00 sec)

mysql> select 4/0;
+------+
| 4/0  |
+------+
| NULL |
+------+
1 row in set, 1 warning (0.00 sec) # 这里产生了一个警告,下面查看下警告信息是什么。

mysql> show warnings;
+---------+------+---------------+
| Level   | Code | Message       |
+---------+------+---------------+
| Warning | 1365 | Division by 0 |
+---------+------+---------------+
1 row in set (0.00 sec)

mysql> select * from t;
+----+------+------+------+------+------+---------------------+---------------------+
| id | a    | b    | x    | y    | z    | c                   | d                   |
+----+------+------+------+------+------+---------------------+---------------------+
|  1 | x1   | y1   |    8 |    0 |   11 | 2021-06-01 14:44:29 | 2021-06-01 17:57:36 |
|  2 | x1   | y1   |    8 |    0 | NULL | 2021-06-01 14:44:29 | 2021-06-01 17:57:42 |
|  3 | x1   | y3   |    8 |    0 |   11 | 2021-06-01 14:44:29 | 2021-06-01 17:57:36 |
|  4 | x2   | y2   |    8 |    0 |   11 | 2021-06-01 14:44:29 | 2021-06-01 17:57:36 |
|  5 | x2   | y3   |    8 |    0 |   11 | 2021-06-01 14:44:29 | 2021-06-01 17:57:36 |
|  6 | x2   | y3   |    8 |    0 |   11 | 2021-06-01 14:44:29 | 2021-06-01 17:57:36 |
|  7 | x3   | y3   | NULL |    1 |    1 | NULL                | 2021-06-01 17:58:26 |
+----+------+------+------+------+------+---------------------+---------------------+
7 rows in set (0.00 sec)

mysql> insert into t(a,b,x,y,z) values ('x4','y4',2/0,2,2); # 插入语句中,有涉及到0作为分母的情况,插入失败。
ERROR 1365 (22012): Division by 0

mysql> update t set z = x / y where id = 3; # 更新语句中,有涉及到0作为分母的情况,更新失败。
ERROR 1365 (22012): Division by 0

mysql>

通过上面的实验,我们可以得知,当sql_mode设置上

ERROR_FOR_DIVISION_BY_ZERO之后,当我们再使用0作为分母的时候,select语句就会出现相应的警告信息,而insert、update语句就是出现相应的错误信息。

建议将sql_mode的参数设置为包含

ERROR_FOR_DIVISION_BY_ZERO,这样可以方便我们检查我们的SQL语句中使用0作为除数的问题。

注意:Message: 'NO_ZERO_DATE', 'NO_ZERO_IN_DATE' and '

ERROR_FOR_DIVISION_BY_ZERO' sql modes should be used with strict mode. They will be merged with strict mode in a future release.,通过这个提示,我们知道在配置这三个参数的时候,需要一起配置上严格模式,也就是需要和STRICT_TRANS_TABLES一起使用才可以。

NO_ZERO_IN_DATE

如果sql_mode的参数列表中包含NO_ZERO_IN_DATE,那么表示日期类型YYYY-MM-DD字段中的MM和DD,都不能出现00这样的月或日,如果查询,则属于非法数据,不允许插入或更新到数据库中。但是,有一种特殊情况:0000-00-00这一种数据属于合法数据。

下面是没有设置NO_ZERO_IN_DATE参数值的sql_mode,如下所示:

mysql> set session sql_mode='PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> show session variables like 'sql_mode'G
*************************** 1. row ***************************
Variable_name: sql_mode
        Value: PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER
1 row in set (0.01 sec)

mysql>

我们来验证一些不正确的日期数据插入到表中的结果是怎样的。如下:

mysql> desc t;
+-------+-------------+------+-----+-------------------+-----------------------------+
| Field | Type        | Null | Key | Default           | Extra                       |
+-------+-------------+------+-----+-------------------+-----------------------------+
| id    | int(11)     | NO   | PRI | NULL              | auto_increment              |
| a     | varchar(16) | YES  |     | NULL              |                             |
| b     | varchar(64) | YES  |     | NULL              |                             |
| x     | int(11)     | YES  |     | NULL              |                             |
| y     | int(11)     | YES  |     | NULL              |                             |
| z     | int(11)     | YES  |     | NULL              |                             |
| c     | date        | YES  |     | NULL              |                             |
| d     | timestamp   | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------+-------------+------+-----+-------------------+-----------------------------+
8 rows in set (0.00 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','without NO_ZERO_IN_DATE',1,1,1,'0000-00-00');
Query OK, 1 row affected (0.01 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','without NO_ZERO_IN_DATE',1,1,1,'2021-00-00');
Query OK, 1 row affected (0.00 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','without NO_ZERO_IN_DATE',1,1,1,'0000-06-00');
Query OK, 1 row affected (0.00 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','without NO_ZERO_IN_DATE',1,1,1,'0000-00-01');
Query OK, 1 row affected (0.00 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','without NO_ZERO_IN_DATE',1,1,1,'0000-06-01');
Query OK, 1 row affected (0.00 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','without NO_ZERO_IN_DATE',1,1,1,'2021-00-01');
Query OK, 1 row affected (0.00 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','without NO_ZERO_IN_DATE',1,1,1,'2021-06-00');
Query OK, 1 row affected (0.00 sec)

mysql> select * from t;
+----+----------+-------------------------+------+------+------+------------+---------------------+
| id | a        | b                       | x    | y    | z    | c          | d                   |
+----+----------+-------------------------+------+------+------+------------+---------------------+
|  1 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 0000-00-00 | 2021-06-02 17:38:49 |
|  2 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 2021-00-00 | 2021-06-02 17:38:49 |
|  3 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 0000-06-00 | 2021-06-02 17:38:49 |
|  4 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 0000-00-01 | 2021-06-02 17:38:49 |
|  5 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 0000-06-01 | 2021-06-02 17:38:49 |
|  6 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 2021-00-01 | 2021-06-02 17:38:49 |
|  7 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 2021-06-00 | 2021-06-02 17:38:50 |
+----+----------+-------------------------+------+------+------+------------+---------------------+
7 rows in set (0.01 sec)

mysql>

通过上面的实验结果可以发现,对于YYYY-MM-DD日期格式的数据,不管YYYY和MM还是DD,它们的值都可以为0000或者00。这些数的都是脏数据,我们正常情况是不希望这样的数据可以成功插入表中。下面我们看下在sql_mode中配置上NO_ZERO_IN_DATE之后,这些数据是否可以再次插入成功。

下面设置sql_mode的值中,增加NO_ZERO_IN_DATE参数,如下:

mysql> set session sql_mode = 'PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,NO_AUTO_CREATE_USER,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ZERO_IN_DATE';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> show session variables like 'sql_mode'G
*************************** 1. row ***************************
Variable_name: sql_mode
        Value: PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER
1 row in set (0.01 sec)

mysql>

再次插入数据看下结果:

mysql>
mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_IN_DATE',2,2,2,'0000-00-00');# 成功,月份MM和日DD都为00,同时年份也为0,这是一种特殊的case。
Query OK, 1 row affected (0.01 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_IN_DATE',2,2,2,'2021-00-00'); # 失败了,因为月份MM为00,不满足月和日都不为0的要求。
ERROR 1292 (22007): Incorrect date value: '2021-00-00' for column 'c' at row 1
mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_IN_DATE',2,2,2,'0000-06-00'); # 失败了,因为日DD为00,不满足月和日都不为0的要求。
ERROR 1292 (22007): Incorrect date value: '0000-00-06' for column 'c' at row 1
mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_IN_DATE',2,2,2,'0000-00-01'); # 失败了,因为月份MM为00,不满足月和日都不为0的要求。
ERROR 1292 (22007): Incorrect date value: '0000-01-00' for column 'c' at row 1
mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_IN_DATE',2,2,2,'0000-06-01'); # 成功,月份MM和日DD都不为00
Query OK, 1 row affected (0.00 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_IN_DATE',2,2,2,'2021-00-01'); # 失败了,因为月份MM为00,不满足月和日都不为0的要求。
ERROR 1292 (22007): Incorrect date value: '2021-01-00' for column 'c' at row 1
mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_IN_DATE',2,2,2,'2021-06-00'); # 失败了,因为日DD为00,不满足月和日都不为0的要求。
ERROR 1292 (22007): Incorrect date value: '2021-00-06' for column 'c' at row 1

mysql> select * from t;
+----+----------+-------------------------+------+------+------+------------+---------------------+
| id | a        | b                       | x    | y    | z    | c          | d                   |
+----+----------+-------------------------+------+------+------+------------+---------------------+
|  1 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 0000-00-00 | 2021-06-02 17:38:49 |
|  2 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 2021-00-00 | 2021-06-02 17:38:49 |
|  3 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 0000-06-00 | 2021-06-02 17:38:49 |
|  4 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 0000-00-01 | 2021-06-02 17:38:49 |
|  5 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 0000-06-01 | 2021-06-02 17:38:49 |
|  6 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 2021-00-01 | 2021-06-02 17:38:49 |
|  7 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 2021-06-00 | 2021-06-02 17:38:50 |
|  8 | sql_mode | with NO_ZERO_IN_DATE    |    2 |    2 |    2 | 0000-00-00 | 2021-06-02 17:42:08 |
|  9 | sql_mode | with NO_ZERO_IN_DATE    |    2 |    2 |    2 | 0000-06-01 | 2021-06-02 17:42:08 |
+----+----------+-------------------------+------+------+------+------------+---------------------+
9 rows in set (0.00 sec)

mysql>

通过上面的实验,我们发现当sql_mode配置上NO_ZERO_IN_DATE之后,在YYYY-MM-DD日期格式的字段中,除了0000-00-00可以成功插入之外,其他的任何日期中,只要是MM或者DD两个位置有任何一个位置是00,那么就会认为这个日期是非法日期。

综上两个实验,我们可以得出结论:NO_ZERO_IN_DATE的作用是,验证月和日两个位置上的数字都不能为00的日期才可以正常插入到表中,它不管年份的值是多少。但是有一种特殊情况除外,就是当年份YYYY的值为0000的时候,即便它的月和日都为00,也就是日期为:0000-00-00时,这一条特殊的数据是可以插入的。

NO_ZERO_DATE

当sql_mode中包含NO_ZERO_DATE参数值的时候,表示日期格式YYYY-MM-DD的数据中,年份YYYY不允许出现0000这样的年份,如果出现了表示这是非法的日期,不允许插入或更新到表中。

设置sql_mode的值,让其不包含NO_ZERO_DATE值,如下所示,注意:此时我们把前面设置的NO_ZERO_IN_DATE参数也去掉,避免NO_ZERO_IN_DATE参数值带来的影响。

mysql> set session sql_mode='PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER';
Query OK, 0 rows affected (0.00 sec)

mysql> show session variables like 'sql_mode'G
*************************** 1. row ***************************
Variable_name: sql_mode
        Value: PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER
1 row in set (0.00 sec)

mysql>

在sql_mode没有配置NO_ZERO_DATE和NO_ZERO_IN_DATE参数的情况下,插入非法的日期数据,看是否才可以插入成功:

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','without NO_ZERO_DATE',3,3,3,'0000-00-00');
Query OK, 1 row affected (0.00 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','without NO_ZERO_DATE',3,3,3,'2021-00-00');
Query OK, 1 row affected (0.00 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','without NO_ZERO_DATE',3,3,3,'0000-06-00');
Query OK, 1 row affected (0.00 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','without NO_ZERO_DATE',3,3,3,'0000-00-01');
Query OK, 1 row affected (0.00 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','without NO_ZERO_DATE',3,3,3,'0000-06-01');
Query OK, 1 row affected (0.00 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','without NO_ZERO_DATE',3,3,3,'2021-00-01');
Query OK, 1 row affected (0.01 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','without NO_ZERO_DATE',3,3,3,'2021-06-00');
Query OK, 1 row affected (0.00 sec)

mysql> select * from t;
+----+----------+-------------------------+------+------+------+------------+---------------------+
| id | a        | b                       | x    | y    | z    | c          | d                   |
+----+----------+-------------------------+------+------+------+------------+---------------------+
|  1 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 0000-00-00 | 2021-06-02 17:38:49 |
|  2 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 2021-00-00 | 2021-06-02 17:38:49 |
|  3 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 0000-06-00 | 2021-06-02 17:38:49 |
|  4 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 0000-00-01 | 2021-06-02 17:38:49 |
|  5 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 0000-06-01 | 2021-06-02 17:38:49 |
|  6 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 2021-00-01 | 2021-06-02 17:38:49 |
|  7 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 2021-06-00 | 2021-06-02 17:38:50 |
|  8 | sql_mode | with NO_ZERO_IN_DATE    |    2 |    2 |    2 | 0000-00-00 | 2021-06-02 17:42:08 |
|  9 | sql_mode | with NO_ZERO_IN_DATE    |    2 |    2 |    2 | 0000-06-01 | 2021-06-02 17:42:08 |
| 10 | sql_mode | without NO_ZERO_DATE    |    3 |    3 |    3 | 0000-00-00 | 2021-06-02 17:53:11 |
| 11 | sql_mode | without NO_ZERO_DATE    |    3 |    3 |    3 | 2021-00-00 | 2021-06-02 17:53:11 |
| 12 | sql_mode | without NO_ZERO_DATE    |    3 |    3 |    3 | 0000-06-00 | 2021-06-02 17:53:11 |
| 13 | sql_mode | without NO_ZERO_DATE    |    3 |    3 |    3 | 0000-00-01 | 2021-06-02 17:53:11 |
| 14 | sql_mode | without NO_ZERO_DATE    |    3 |    3 |    3 | 0000-06-01 | 2021-06-02 17:53:11 |
| 15 | sql_mode | without NO_ZERO_DATE    |    3 |    3 |    3 | 2021-00-01 | 2021-06-02 17:53:11 |
| 16 | sql_mode | without NO_ZERO_DATE    |    3 |    3 |    3 | 2021-06-00 | 2021-06-02 17:53:12 |
+----+----------+-------------------------+------+------+------+------------+---------------------+
16 rows in set (0.00 sec)

mysql>

通过上面的实验,发现非法的日期是可以插入成功了。下面我们增加上NO_ZERO_DATE参数后,再进行插入实验。注意:此时我们把前面设置的NO_ZERO_IN_DATE参数需要去掉,单独验证NO_ZERO_DATE参数值的功能。避免NO_ZERO_IN_DATE参数值带来的影响。

mysql> set session sql_mode='PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ZERO_DATE';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> show session variables like 'sql_mode'G
*************************** 1. row ***************************
Variable_name: sql_mode
        Value: PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER
1 row in set (0.01 sec)

mysql>

在sql_mode只配置NO_ZERO_DATE,没有配置NO_ZERO_IN_DATE参数的情况下,插入非法的日期数据,看是否才可以插入成功:

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_DATE',4,4,4,'0000-00-00'); # 失败,因为年月日三个部分都是0。
ERROR 1292 (22007): Incorrect date value: '0000-00-00' for column 'c' at row 1
mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_DATE',4,4,4,'2021-00-00'); # 成功,因为年月日三个部分中,存在至少一个不为0的部分。
Query OK, 1 row affected (0.00 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_DATE',4,4,4,'0000-06-00');# 成功,因为年月日三个部分中,存在至少一个不为0的部分。
Query OK, 1 row affected (0.00 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_DATE',4,4,4,'0000-00-01');# 成功,因为年月日三个部分中,存在至少一个不为0的部分。
Query OK, 1 row affected (0.00 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_DATE',4,4,4,'0000-06-01');# 成功,因为年月日三个部分中,存在至少一个不为0的部分。
Query OK, 1 row affected (0.00 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_DATE',4,4,4,'2021-00-01');# 成功,因为年月日三个部分中,存在至少一个不为0的部分。
Query OK, 1 row affected (0.00 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_DATE',4,4,4,'2021-06-00');# 成功,因为年月日三个部分中,存在至少一个不为0的部分。
Query OK, 1 row affected (0.00 sec)

mysql> select * from t;
+----+----------+-------------------------+------+------+------+------------+---------------------+
| id | a        | b                       | x    | y    | z    | c          | d                   |
+----+----------+-------------------------+------+------+------+------------+---------------------+
|  1 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 0000-00-00 | 2021-06-02 17:38:49 |
|  2 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 2021-00-00 | 2021-06-02 17:38:49 |
|  3 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 0000-06-00 | 2021-06-02 17:38:49 |
|  4 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 0000-00-01 | 2021-06-02 17:38:49 |
|  5 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 0000-06-01 | 2021-06-02 17:38:49 |
|  6 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 2021-00-01 | 2021-06-02 17:38:49 |
|  7 | sql_mode | without NO_ZERO_IN_DATE |    1 |    1 |    1 | 2021-06-00 | 2021-06-02 17:38:50 |
|  8 | sql_mode | with NO_ZERO_IN_DATE    |    2 |    2 |    2 | 0000-00-00 | 2021-06-02 17:42:08 |
|  9 | sql_mode | with NO_ZERO_IN_DATE    |    2 |    2 |    2 | 0000-06-01 | 2021-06-02 17:42:08 |
| 10 | sql_mode | without NO_ZERO_DATE    |    3 |    3 |    3 | 0000-00-00 | 2021-06-02 17:53:11 |
| 11 | sql_mode | without NO_ZERO_DATE    |    3 |    3 |    3 | 2021-00-00 | 2021-06-02 17:53:11 |
| 12 | sql_mode | without NO_ZERO_DATE    |    3 |    3 |    3 | 0000-06-00 | 2021-06-02 17:53:11 |
| 13 | sql_mode | without NO_ZERO_DATE    |    3 |    3 |    3 | 0000-00-01 | 2021-06-02 17:53:11 |
| 14 | sql_mode | without NO_ZERO_DATE    |    3 |    3 |    3 | 0000-06-01 | 2021-06-02 17:53:11 |
| 15 | sql_mode | without NO_ZERO_DATE    |    3 |    3 |    3 | 2021-00-01 | 2021-06-02 17:53:11 |
| 16 | sql_mode | without NO_ZERO_DATE    |    3 |    3 |    3 | 2021-06-00 | 2021-06-02 17:53:12 |
| 17 | sql_mode | with NO_ZERO_DATE       |    4 |    4 |    4 | 2021-00-00 | 2021-06-02 17:56:07 |
| 18 | sql_mode | with NO_ZERO_DATE       |    4 |    4 |    4 | 0000-06-00 | 2021-06-02 17:56:07 |
| 19 | sql_mode | with NO_ZERO_DATE       |    4 |    4 |    4 | 0000-00-01 | 2021-06-02 17:56:07 |
| 20 | sql_mode | with NO_ZERO_DATE       |    4 |    4 |    4 | 0000-06-01 | 2021-06-02 17:56:07 |
| 21 | sql_mode | with NO_ZERO_DATE       |    4 |    4 |    4 | 2021-00-01 | 2021-06-02 17:56:07 |
| 22 | sql_mode | with NO_ZERO_DATE       |    4 |    4 |    4 | 2021-06-00 | 2021-06-02 17:56:07 |
+----+----------+-------------------------+------+------+------+------------+---------------------+
22 rows in set (0.00 sec)

mysql>

通过上面的实验我们发现,在sql_mode总包含NO_ZERO_DATE参数后,对于日期格式YYYY-MM-DD的验证,只要这YYYY、MM、DD三部分中存在任何一个或多个不为0的情况下,日期都是可以正常插入到数据表中的。也就是说,NO_ZERO_DATE参数的作用是控制YYYY-MM-DD年月日中至少有一个不为0才算是合法的日期格式。只有年月日三个部分全都为0的时候,才会认为是非法日期。

基于前面NO_ZERO_IN_DATE的实验,我们已经知道,在sql_mode中包含NO_ZERO_IN_DATE的设置的情况想,在 YYYY-MM-DD日期中,如果MM或DD任何一个位置出现00,则会非法的日期。但是针对一种特殊的情况:0000-00-00,这样的一个日期它并不认为是非法日期,对于年份全部为0这样的日期是可以插入表中的。那么如何排除这样的一种日期呢?参数值NO_ZERO_DATE的作用就是用来过滤这个情况的。

下面我们来实验一下,在sql_mode中同时配置上NO_ZERO_DATE和NO_ZERO_IN_DATE两个值,我们再来进一步测试一下0000-00-00这样的非法日期是否仍然可以正常插入到表中:

mysql> set session sql_mode='PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ZERO_IN_DATE,NO_ZERO_DATE';
Query OK, 0 rows affected (0.00 sec)

mysql> show session variables like 'sql_mode'G
*************************** 1. row ***************************
Variable_name: sql_mode
        Value: PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER
1 row in set (0.00 sec)

mysql>

下面我们分别插入在前面实验中sql_mode包含NO_ZERO_IN_DATE参数值的时候,成功插入的两条数据0000-00-00和0000-06-01是否还可以插入成功。

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_IN_DATE and NO_ZERO_DATE',5,5,5,'0000-00-00'); # 失败,因为NO_ZERO_DATE参数控制年月日三个部分至少有一个部分不为0才可以插入成功。
ERROR 1292 (22007): Incorrect date value: '0000-00-00' for column 'c' at row 1
mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_IN_DATE and NO_ZERO_DATE',5,5,5,'2021-00-00'); # 失败,因为NO_ZERO_IN_DATE参数控制,月和日必须都不为0才可以插入成功。
ERROR 1292 (22007): Incorrect date value: '2021-00-00' for column 'c' at row 1
mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_IN_DATE and NO_ZERO_DATE',5,5,5,'0000-06-00'); # 失败,因为NO_ZERO_IN_DATE参数控制,月和日必须都不为0才可以插入成功。
ERROR 1292 (22007): Incorrect date value: '0000-06-00' for column 'c' at row 1
mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_IN_DATE and NO_ZERO_DATE',5,5,5,'0000-00-01'); # 失败,因为NO_ZERO_IN_DATE参数控制,月和日必须都不为0才可以插入成功。
ERROR 1292 (22007): Incorrect date value: '0000-00-01' for column 'c' at row 1
mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_IN_DATE and NO_ZERO_DATE',5,5,5,'0000-06-01'); # 成功,因为NO_ZERO_IN_DATE参数控制,月和日必须都不为0才可以插入成功,满足这个条件,同时NO_ZERO_DATE参数控制年月日三个部分至少有一个部分不为0,也满足这个条件,所以可以插入成功。
Query OK, 1 row affected (0.00 sec)

mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_IN_DATE and NO_ZERO_DATE',5,5,5,'2021-00-01'); # 失败,因为NO_ZERO_IN_DATE参数控制,月和日必须都不为0才可以插入成功。
ERROR 1292 (22007): Incorrect date value: '2021-00-01' for column 'c' at row 1
mysql> insert into t(a,b,x,y,z,c) values('sql_mode','with NO_ZERO_IN_DATE and NO_ZERO_DATE',5,5,5,'2021-06-00'); # 失败,因为NO_ZERO_IN_DATE参数控制,月和日必须都不为0才可以插入成功。
ERROR 1292 (22007): Incorrect date value: '2021-06-00' for column 'c' at row 1
mysql>
mysql> select * from t;
+----+----------+---------------------------------------+------+------+------+------------+---------------------+
| id | a        | b                                     | x    | y    | z    | c          | d                   |
+----+----------+---------------------------------------+------+------+------+------------+---------------------+
|  1 | sql_mode | without NO_ZERO_IN_DATE               |    1 |    1 |    1 | 0000-00-00 | 2021-06-02 17:38:49 |
|  2 | sql_mode | without NO_ZERO_IN_DATE               |    1 |    1 |    1 | 2021-00-00 | 2021-06-02 17:38:49 |
|  3 | sql_mode | without NO_ZERO_IN_DATE               |    1 |    1 |    1 | 0000-06-00 | 2021-06-02 17:38:49 |
|  4 | sql_mode | without NO_ZERO_IN_DATE               |    1 |    1 |    1 | 0000-00-01 | 2021-06-02 17:38:49 |
|  5 | sql_mode | without NO_ZERO_IN_DATE               |    1 |    1 |    1 | 0000-06-01 | 2021-06-02 17:38:49 |
|  6 | sql_mode | without NO_ZERO_IN_DATE               |    1 |    1 |    1 | 2021-00-01 | 2021-06-02 17:38:49 |
|  7 | sql_mode | without NO_ZERO_IN_DATE               |    1 |    1 |    1 | 2021-06-00 | 2021-06-02 17:38:50 |
|  8 | sql_mode | with NO_ZERO_IN_DATE                  |    2 |    2 |    2 | 0000-00-00 | 2021-06-02 17:42:08 |
|  9 | sql_mode | with NO_ZERO_IN_DATE                  |    2 |    2 |    2 | 0000-06-01 | 2021-06-02 17:42:08 |
| 10 | sql_mode | without NO_ZERO_DATE                  |    3 |    3 |    3 | 0000-00-00 | 2021-06-02 17:53:11 |
| 11 | sql_mode | without NO_ZERO_DATE                  |    3 |    3 |    3 | 2021-00-00 | 2021-06-02 17:53:11 |
| 12 | sql_mode | without NO_ZERO_DATE                  |    3 |    3 |    3 | 0000-06-00 | 2021-06-02 17:53:11 |
| 13 | sql_mode | without NO_ZERO_DATE                  |    3 |    3 |    3 | 0000-00-01 | 2021-06-02 17:53:11 |
| 14 | sql_mode | without NO_ZERO_DATE                  |    3 |    3 |    3 | 0000-06-01 | 2021-06-02 17:53:11 |
| 15 | sql_mode | without NO_ZERO_DATE                  |    3 |    3 |    3 | 2021-00-01 | 2021-06-02 17:53:11 |
| 16 | sql_mode | without NO_ZERO_DATE                  |    3 |    3 |    3 | 2021-06-00 | 2021-06-02 17:53:12 |
| 17 | sql_mode | with NO_ZERO_DATE                     |    4 |    4 |    4 | 2021-00-00 | 2021-06-02 17:56:07 |
| 18 | sql_mode | with NO_ZERO_DATE                     |    4 |    4 |    4 | 0000-06-00 | 2021-06-02 17:56:07 |
| 19 | sql_mode | with NO_ZERO_DATE                     |    4 |    4 |    4 | 0000-00-01 | 2021-06-02 17:56:07 |
| 20 | sql_mode | with NO_ZERO_DATE                     |    4 |    4 |    4 | 0000-06-01 | 2021-06-02 17:56:07 |
| 21 | sql_mode | with NO_ZERO_DATE                     |    4 |    4 |    4 | 2021-00-01 | 2021-06-02 17:56:07 |
| 22 | sql_mode | with NO_ZERO_DATE                     |    4 |    4 |    4 | 2021-06-00 | 2021-06-02 17:56:07 |
| 23 | sql_mode | with NO_ZERO_IN_DATE and NO_ZERO_DATE |    5 |    5 |    5 | 0000-06-01 | 2021-06-02 18:09:06 |
+----+----------+---------------------------------------+------+------+------+------------+---------------------+
23 rows in set (0.00 sec)

mysql>

经过上面的实验我们发现,在sql_mode增加上NO_ZERO_DATE参数后,对于原本只有NO_ZERO_IN_DATE参数时,可以插入成功的0000-MM-DD这样的非法日期,此时在NO_ZERO_DATE参数的作用下,也不能插入成功了。但是对于0000-06-01这样的日期还是可以插入成功的。因为这种格式的日期,同时满足了NO_ZERO_IN_DATE和NO_ZERO_DATE两个参数的要求。

小结:NO_ZERO_DATE的作用是控制YYYY-MM-DD日期中的年月日三个部分不能全都为0,需要至少存在一个不是0的部分才认为是合法的日期格式;而NO_ZERO_IN_DATE控制的是MM和DD都不能为00才认为是合法的日期格式,月和日两部分任何一个部分为0就认为是非法的日期。在同时设置了NO_ZERO_DATE和NO_ZERO_IN_DATE之后,可以避免0000-00-00这样的日期插入,但是仍然可以插入0000-06-01年份为0的日期数据。

另外一点需要注意:在配置NO_ZERO_DATE和NO_ZERO_IN_DATE参数的时候,需要和STRICT_TRANS_TABLES参数一起配合使用。如果开启了:NO_ZERO_DATA、NO_ZERO_IN_DATE、

ERROR_FOR_DIVISION_BY_ZERO,则需要开启STRICT_TRANS_TABLES,反之亦然。否则开启任何一边,在设置完sql_mode参数值后,后面都会出现警告信息,可以通过show warnings命令查看警告信息的详细内容。

NO_ENGINE_SUBSTITUTION

在sql_mode中配置上NO_ENGINE_SUBSTITUTION参数之后,在我们创建表的时候,如果为表指定的存储引擎在当前的MySQL实例中并不支持或者没有启用的话,那么表会创建失败并抛出一个error错误信息。如果没有配置这个参数,那么当创建一个不支持的存储引擎表的时候,表或创建成功并且会抛出一个warning警告信息,会使用MySQL中默认的存储引擎来创建这个表。

下面我们来看一下在sql_mode在没有设置配置NO_ENGINE_SUBSUITUTION参数的时候,创建一个不支持的存储引擎的表,会有什么效果。

mysql> show session variables like 'sql_mode'G
*************************** 1. row ***************************
Variable_name: sql_mode
        Value: PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER
1 row in set (0.00 sec)

mysql>

查看当前MySQL实例锁支持的存储引擎有哪些,使用下面的show engines命令即可:

mysql> show engines;
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| Engine             | Support | Comment                                                        | Transactions | XA   | Savepoints |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| InnoDB             | DEFAULT | Supports transactions, row-level locking, and foreign keys     | YES          | YES  | YES        |
| MRG_MYISAM         | YES     | Collection of identical MyISAM tables                          | NO           | NO   | NO         |
| MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables      | NO           | NO   | NO         |
| BLACKHOLE          | YES     | /dev/null storage engine (anything you write to it disAppears) | NO           | NO   | NO         |
| MyISAM             | YES     | MyISAM storage engine                                          | NO           | NO   | NO         |
| CSV                | YES     | CSV storage engine                                             | NO           | NO   | NO         |
| ARCHIVE            | YES     | Archive storage engine                                         | NO           | NO   | NO         |
| PERFORMANCE_SCHEMA | YES     | Performance Schema                                             | NO           | NO   | NO         |
| FEDERATED          | NO      | Federated MySQL storage engine                                 | NULL         | NULL | NULL       |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
9 rows in set (0.01 sec)

mysql>

通过上面的输出可以看出当前的MySQL实例并不支持NDB存储引擎,并且其默认的存储引擎为InnoDB,下面我们创建一个NDB存储引擎的表tab1_ndb,看下是否可以创建成功。

mysql> create table tab1_ndb(id int, a varchar(16))engine=ndb;
Query OK, 0 rows affected, 2 warnings (0.03 sec)

mysql> show warnings;
+---------+------+-------------------------------------------------+
| Level   | Code | Message                                         |
+---------+------+-------------------------------------------------+
| Warning | 1286 | Unknown storage engine 'ndb'                    |
| Warning | 1266 | Using storage engine InnoDB for table 'tab1_ndb' |
+---------+------+-------------------------------------------------+
2 rows in set (0.00 sec)

mysql> show create table tab1_ndbG
*************************** 1. row ***************************
       Table: tab1_ndb
Create Table: CREATE TABLE "tab1_ndb" (
  "id" int(11) DEFAULT NULL,
  "a" varchar(16) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

mysql>

通过上面的输出,可以看出NDB存储引擎的表虽然被创建了,但是它的存储因并不是我们想要的NDB存储引擎,而是使用了当前MySQL实例中默认的InnoDB存储引擎的表来代替的NDB存储引擎。并且在创建表的时候,抛出了两条warning信息,提示我们说NDB存储引擎不支持,使用了InnoDB存储引擎来代替了。

上面就是在sql_mode没有配置NO_ENGINE_SUBSTITUTION参数的时候的现象。下面我们来为sql_mode配置上NO_ENGINE_SUBSTITUTION参数后,做同样的实验,看下是否可以创建成功对应的表。

mysql> set session sql_mode='PIPES_AS_CONCAT,ANSI_QUOTES,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';
Query OK, 0 rows affected (0.01 sec)

mysql> show session variables like 'sql_mode'G
*************************** 1. row ***************************
Variable_name: sql_mode
        Value: PIPES_AS_CONCAT,ANSI_QUOTES,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
1 row in set (0.00 sec)

mysql>

创建NDB存储引擎的表,看下是否可以创建成,实验过程如下:

mysql> create table tab2_ndb(id int, a varchar(16))engine=ndb;
ERROR 1286 (42000): Unknown storage engine 'ndb'
mysql>

通过上面的输出可以知道,NDB存储引擎的表创建失败,直接抛出了一个错误信息。这就是在sql_mode中配置了NO_ENGINE_SUBSTITUTION参数后的效果。建议在sql_mode中配置这一个参数,这样可以避免我们期望的表创建成功能了,但是存储引擎并不是我们想要的存储引擎,与其以后会采坑,倒不如现在直接让其创建失败并抛出错误异常信息,让我们知道我们的表创建失败,这样会及时的发现问题解决问题。

NO_AUTO_VALUE_ON_ZERO

在sql_mode中没有配置NO_AUTO_VALUE_ON_ZERO这个参数的时候,当我们向一个自增的字段中插入数据的时候,如果我们为自增字段赋予的值为0,则不会使用我们指定的值,而是使用自增值来替换我们指定的值0;而当sql_mode配置上NO_AUTO_VALUE_ON_ZERO参数后,则会使用我们指定的0来给自增字段赋值,而不是使用自增序列的值来填充自增字段。

下面的sql_mode中是没有配置NO_AUTO_VALUE_ON_ZERO参数,我们看下当我们向自增的字段中设置0的时候,插入数据会有什么现象。

mysql> show session variables like 'sql_mode'G
*************************** 1. row ***************************
Variable_name: sql_mode
        Value: PIPES_AS_CONCAT,ANSI_QUOTES,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
1 row in set (0.00 sec)

mysql>

下面针对测试的表进行插入数据,这个表的主键是自增主键,我们把主键值设置为0,看下执行效果:

mysql> desc tab1_ndb;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(11)     | NO   | PRI | NULL    | auto_increment |
| a     | varchar(16) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

mysql> insert into tab1_ndb(id,a) values(1,'a');
Query OK, 1 row affected (0.00 sec)

mysql> insert into tab1_ndb(id,a) values(0,'b'); # 主键值设置为0,插入的时候,并没有使用0,而是使用的自增的值2
Query OK, 1 row affected (0.00 sec)

mysql> insert into tab1_ndb(id,a) values(0,'c'); # 主键值设置为0,插入的时候,并没有使用0,而是使用的自增的值3
Query OK, 1 row affected (0.00 sec)

mysql> insert into tab1_ndb(id,a) values(null,'d'); # 主键值设置为null,插入的时候,并没有使用0,而是使用的自增的值4
Query OK, 1 row affected (0.00 sec)

mysql> select * from tab1_ndb; # 查看插入数据后的结果
+----+------+
| id | a    |
+----+------+
|  1 | a    |
|  2 | b    |
|  3 | c    |
|  4 | d    |
+----+------+
4 rows in set (0.00 sec)

mysql>

通过上面的实验,我们可以知道,当我们为自增列的值设置为0的时候,并不会使用0来填充自增字段,而是使用了自增序列的值来填充在自增字段中。这就是在sql_mode中没有配置NO_AUTO_VALUE_ON_ZERO参数的效果。

下面我们为sql_mode配置上NO_AUTO_VALUE_ON_ZERO参数,然后再看下向自增序列中插入0的效果是什么样子的。

mysql> set session sql_mode='PIPES_AS_CONCAT,ANSI_QUOTES,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,NO_AUTO_VALUE_ON_ZERO';
Query OK, 0 rows affected (0.00 sec)

mysql> show session variables like 'sql_mode'G
*************************** 1. row ***************************
Variable_name: sql_mode
        Value: PIPES_AS_CONCAT,ANSI_QUOTES,ONLY_FULL_GROUP_BY,NO_AUTO_VALUE_ON_ZERO,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
1 row in set (0.00 sec)

mysql>

下面继续向tab1_ndb表中插入数据,验证结果:

mysql> select * from tab1_ndb;
+----+------+
| id | a    |
+----+------+
|  1 | a    |
|  2 | b    |
|  3 | c    |
|  4 | d    |
+----+------+
4 rows in set (0.00 sec)

mysql> insert into tab1_ndb(id,a) values(null,'e');
Query OK, 1 row affected (0.00 sec)

mysql> insert into tab1_ndb(id,a) values(0,'f');
Query OK, 1 row affected (0.00 sec)

mysql> insert into tab1_ndb(id,a) values(0,'g');
ERROR 1062 (23000): Duplicate entry '0' for key 'PRIMARY'

mysql> select * from tab1_ndb;
+----+------+
| id | a    |
+----+------+
|  0 | f    |
|  1 | a    |
|  2 | b    |
|  3 | c    |
|  4 | d    |
|  5 | e    |
+----+------+
6 rows in set (0.01 sec)

mysql>

通过上面的实验,我们可以看出在sql_mode中配置上NO_AUTO_VALUE_ON_ZERO后,当我们向一个自增字段插入数据的时候,为其赋值为0后,就是使用这个0而不会再从自增序列中获取对应的自增值了。

另外需要注意的一点:不管是否配置NO_AUTO_VALUE_ON_ZERO参数,对于向自增字段中赋予的null值,都会使用自增序列中的值来填充自增字段,而不会使用null来填充。只要0的时候,才会有所区别。

建议把NO_AUTO_VALUE_ON_ZERO值配置在sql_mode中,这样可以避免向自增字段中插入的值不是我们希望的值,例如我们希望赋予0,但是结果去不是以0的值插入的,而是取得一个自增序列的值替代的0。

总结

结合上面的实验,我们可以对sql_mode的各参数值作出如下的总结:

欢迎转发评论。

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>