首页 » db » MySQL sql_mode参数介绍

MySQL sql_mode参数介绍

 
文章目录

sql_mode用于控制SQL语句的执行方式和行为。通过设置sql_mode,你可以启用或禁用特定的功能和约束,这样可以帮助你根据应用程序的需求调整数据库的行为。

本文所有演示示例,全部基于wlnmp源安装的MySQL8.4.3版本。

MySQL8.4官方参考手册:https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html

附,MySQL my.cnf配置文件生成器:https://dbcnf.wlnmp.com/

MySQL8.4默认值

ONLY_FULL_GROUP_BY
STRICT_TRANS_TABLES
NO_ZERO_IN_DATE
NO_ZERO_DATE
ERROR_FOR_DIVISION_BY_ZERO
NO_ENGINE_SUBSTITUTION

可选值

ALLOW_INVALID_DATES
ANSI_QUOTES
ERROR_FOR_DIVISION_BY_ZERO
HIGH_NOT_PRECEDENCE
IGNORE_SPACE
NO_AUTO_VALUE_ON_ZERO
NO_BACKSLASH_ESCAPES
NO_DIR_IN_CREATE
NO_ENGINE_SUBSTITUTION
NO_UNSIGNED_SUBTRACTION
NO_ZERO_DATE
NO_ZERO_IN_DATE
ONLY_FULL_GROUP_BY
PAD_CHAR_TO_FULL_LENGTH
PIPES_AS_CONCAT
REAL_AS_FLOAT
STRICT_ALL_TABLES
STRICT_TRANS_TABLES
TIME_TRUNCATE_FRACTIONAL

SQL_MODE的严格模式

SQL_MODE的严格模式主要是通过STRICT_ALL_TABLES或STRICT_TRANS_TABLES来实现的。

STRICT_ALL_TABLES:会对事务表(如InnoDB)和非事务表(如MyISAM)中的插入和更新操作进行严格检查。

STRICT_TRANS_TABLES:仅对事务表(如InnoDB)中的插入和更新操作进行严格检查。

在生产环境中,建议使用严格模式(即包含STRICT_ALL_TABLES或STRICT_TRANS_TABLES),以确保数据的有效性和一致性。这有助于避免潜在的数据问题,并提高应用程序的可靠性。

SQL_MODE模式修改

基于会话临时生效

等同于

基于全局临时生效

永久生效

修改MySQL my.cnf配置文件

重启MySQL生效

查询当前SQL_MODE模式

ONLY_FULL_GROUP_BY

ONLY_FULL_GROUP_BY参数在默认情况下是启用的,用于确保在GROUP BY语句SELECT里的列都包含在GROUP BY里。这个模式有助于数据准确性,避免非法数据插入。

从老版本MySQL升级到新版本时,很大可能性会出现无效查询异常的sql,这是因为从MySQL5.7.5版本开始,ONLY_FULL_GROUP_BY为默认值启用,见官方说明:https://dev.mysql.com/doc/refman/5.7/en/upgrading-from-previous-series.html。

启用ONLY_FULL_GROUP_BY SQL模式时,SELECT语句中的字段必须遵循以下规则:

1、分组字段:所有在SELECT中列出的字段必须是GROUP BY子句中指定的字段。

2、聚合函数:如果要在SELECT中使用的字段未出现在GROUP BY子句中,则必须使用聚合函数进行包裹。这些聚合函数包括SUM()、AVG()、COUNT()等。

演示示例

创建数据库

使用数据库

创建employees表

插入一些数据

查询下当前表结构

启用ONLY_FULL_GROUP_BY

查看当前 sql_mode

有效的查询

通过分组字段方式查询

通过聚合函数方式查询

无效的查询

报错ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'company.employees.salary' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

在这个查询中,salary列没有被聚合。salary可能在同一个部门中有多个不同的值,但在GROUP BY的上下文中,MySQL不知道应该如何处理这些不同的salary值。因为salary没有在GROUP BY中列出,也没有使用聚合函数(如 SUM(salary)、AVG(salary) 等),所以MySQL无法确定应该返回哪个salary值。

禁用ONLY_FULL_GROUP_BY

此时我们再执行无效的查询

可以看到,此时查询将不会报错,但是会导致潜在的数据不一致或逻辑错误的结果。

建议生产环境中启用ONLY_FULL_GROUP_BY。

STRICT_TRANS_TABLES

STRICT_TRANS_TABLES参数在默认情况下是启用的,它会对事务表中插入或更新操作的值进行严格检查。如果某个列的值不符合该列的数据类型或约束条件,MySQL将会拒绝该操作并抛出错误,而不是默默地将值截断或转换为默认值。

演示示例

启用STRICT_TRANS_TABLES

查询当前SQL模式

在数据库中创建一个users表

有效的插入

无效的插入

报错ERROR 1366 (HY000): Incorrect integer value: 'five' for column 'age' at row 1

此时插入失败,因为age列期望一个整数,但插入的是字符串。

禁用STRICT_TRANS_TABLES

此时我们再执行无效的插入或更新

此时插入或更新操作都会成功,但有警告,查询下表结构

因为age列是INT类型,所以MySQL会自动将字符串'five'自动转换为0。

如果age列使用TINYINT类型,当插入或更新字段大于TINYINT范围时,MySQL可能会将age自动截断为127。

建议生产环境中启用STRICT_TRANS_TABLES。

NO_ZERO_IN_DATE

NO_ZERO_IN_DATE参数在默认情况下是启用的,用于允许年份为零,但是月份或日期为零(如2025-00-01或2025-01-00),MySQL会将它们视为有效并存储为'0000-00-00'。为防止插入无效的日期,建议搭配严格模式一起使用。

演示示例

启用NO_ZERO_IN_DATE

查询当前SQL模式

创建一个日期测试表

插入0000年份、无效月份或日期

插入成功,前两条插入有警告,查询结果

当年份为0000,月份和日期都不为零时,月份和日期都正常显示。

当出现月份或日期为0时,都将被存储为'0000-00-00'。

启用NO_ZERO_IN_DATE并启用严格模式

插入0000年份、无效月份或日期

报错ERROR 1292 (22007): Incorrect date value: '2025-00-15' for column 'event_date' at row 1

报错ERROR 1292 (22007): Incorrect date value: '2025-01-00' for column 'event_date' at row 1

成功插入

禁用NO_ZERO_IN_DATE

查询当前SQL模式

插入无效月份或日期

插入成功,查询结果

你会发现月份或日期即使为零,也会存储零月份零日期。

建议将NO_ZERO_IN_DATE、STRICT_TRANS_TABLES、NO_ZERO_DATE一起使用。这样可以确保在遇到无效日期时会引发错误,而不是默默地将其存储为0000-00-00,建议生产环境中启用。

NO_ZERO_DATE

NO_ZERO_DATE参数在默认情况下启用的,用于是否允许'0000-00-00'作为有效日期,如果禁用NO_ZERO_DATE,将允许'0000-00-00'日期,前提是搭配严格模式使用。

演示示例

启用NO_ZERO_DATE并启用严格模式

查询当前SQL模式

创建一个日期测试表

插入'0000-00-00'

报错ERROR 1292 (22007): Incorrect date value: '0000-00-00' for column 'event_date' at row 1

禁用NO_ZERO_DATE

插入'0000-00-00'

插入成功

查询结果

建议将NO_ZERO_DATE、NO_ZERO_IN_DATE、STRICT_TRANS_TABLES一起使用。可以有效地减少插入无效日期的风险,确保数据的一致性和有效性,建议生产环境中启用。

ERROR_FOR_DIVISION_BY_ZERO

ERROR_FOR_DIVISION_BY_ZERO参数在默认情况下是启用的,用于控制数据更改操作(INSERT、UPDATE)以及MOD(N,0)执行除以零操作时数据库的行为,为了帮助开发者避免潜在的计算错误,并确保数据的完整性。

演示示例

启用ERROR_FOR_DIVISION_BY_ZERO

查询当前SQL模式

使用INSERT和UPDATE语句,先创建一个演示的表

插入除以0的结果

插入成功,值为NULL,提示警告

禁用ERROR_FOR_DIVISION_BY_ZERO

插入除以0的结果

插入成功,值为NULL,没有警告

启用ERROR_FOR_DIVISION_BY_ZERO并同时开启严格模式

插入除以0的结果

报错ERROR 1365 (22012): Division by 0

更新操作

报错ERROR 1365 (22012): Division by 0

建议将ERROR_FOR_DIVISION_BY_ZERO、STRICT_TRANS_TABLES一起使用。这对于确保数据的完整性和避免潜在的计算错误至关重要,建议生产环境中启用。

NO_ENGINE_SUBSTITUTION

NO_ENGINE_SUBSTITUTION参数在默认情况下是启用的,控制了在SQL语句(如CREATE TABLE或ALTER TABLE)指定的存储引擎不可用时,是否自动替换默认存储引擎。

演示示例

启用NO_ENGINE_SUBSTITUTION

创建库表,使用一个不存在的存储引擎

报错ERROR 1286 (42000): Unknown storage engine 'MyISAM_NOEXIST'

修改表,使用一个不存在的存储引擎

报错ERROR 1286 (42000): Unknown storage engine 'MyISAM_NOEXIST'

禁用NO_ENGINE_SUBSTITUTION

创建一个表,使用一个不存在的存储引擎

创建成功,提示警告

查看test_db数据库中test_table表使用了哪个存储引擎

通过查询结果看到,我们在创建test_table表时,指定了一个不存在的存储引擎,因为MyISAM_NOEXIST引擎并不存在,所以MySQL将其转化成了默认的引擎。如果我们ALTER TABLE更改指定了一个不存在的引擎,表不会被成功修改。

我们想象一个场景,在创建时指定了MyISAM引擎,但是字母打错了,一样会创建成功,但是最终使用的引擎就不一致了,所以建议生产环境中启用NO_ENGINE_SUBSTITUTION。

ALLOW_INVALID_DATES

ALLOW_INVALID_DATES参数在默认情况下是禁用的,它仅检查月份是否在1-12范围内,以及日期是否在1-31范围内,该参数只适用于DATE和DATETIME列。

1、禁用时,非法日期(如'2025-02-31'),将被转换为'0000-00-00'。

2、禁用并启用严格模式时,非法日期将生成错误。

3、启用时,只检查月份是否在1-12范围内,日期是否在1-31范围内,并不检查日期是否正确(如'2025-02-31'被认为是正确的),超出范围将被转换为'0000-00-00'。

演示示例

禁用ALLOW_INVALID_DATES

创建测试表

插入非法日期

执行成功,两条sql均提示警告

查询插入结果

非法日期将被转换为'0000-00-00'。

禁用ALLOW_INVALID_DATES并开启严格模式

插入非法日期

报错ERROR 1292 (22007): Incorrect date value: '2025-02-31' for column 'date_value' at row 1

启用ALLOW_INVALID_DATES

插入非法日期

执行成功

执行成功,提示警告

查询插入结果

未超出月份1-12范围,日期1-31范围,哪怕日期错误也会被存储,超出范围将被转换为'0000-00-00'。

建议在生产环境中禁用ALLOW_INVALID_DATES,如果需要更灵活的处理方式,可以考虑在应用层实现验证逻辑,而不是在数据库中放宽约束。

ANSI_QUOTES

ANSI_QUOTES参数在默认情况下是禁用的,用于控制SQL语法中双引号 (") 的行为。

禁用时,双引号会被当作字符串的开始和结束符号,不能用来引用标识符(如表名和列名)。

启用时,双引号会被当作标识符(如表名和列名)的引用符号,不能用来引用字符串。

演示示例

禁用ANSI_QUOTES

使用双引号引用标识符创建表

报错ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"my_table2" (
"id" INT,
"name" VARCHAR(100)
)' at line 1

正确创建方法

使用双引号引用标识符插入数据

报错ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"my_table2" ("id", "name") VALUES (1, 'zhangsan')' at line 1

使用双引号引用字符串插入数据

执行成功

使用单引号引用字符串插入数据

执行成功

启用ANSI_QUOTES

使用双引号引用标识符创建表

执行成功

使用双引号引用字符串插入数据

报错ERROR 1054 (42S22): Unknown column 'zhangsan' in 'field list'

使用双引号引用标识符插入数据

执行成功

使用单引号引用字符串插入数据

执行成功

建议在生产环境中,结合实际场景决定是否启用ANSI_QUOTES参数。

HIGH_NOT_PRECEDENCE

HIGH_NOT_PRECEDENCE参数在默认情况下是禁用的,影响NOT运算符与其他逻辑运算符(如 BETWEEN、AND、OR等)之间的优先级。

禁用时:NOT运算符的优先级低于BETWEEN运算符。

启用时:NOT运算符的优先级高于BETWEEN运算符。

演示示例

禁用HIGH_NOT_PRECEDENCE

禁用时NOT a BETWEEN b AND c被解析为NOT (a BETWEEN b AND c)

执行以下查询

根据解析公式,这里的表达式NOT 1 BETWEEN -5 AND 5实际上被解释为NOT (1 BETWEEN -5 AND 5)。

1 BETWEEN -5 AND 5的结果是TRUE(因为1在-5和5之间),因此NOT TRUE就是FALSE,在MySQL中表示为0。

启用HIGH_NOT_PRECEDENCE

启用时NOT a BETWEEN b AND c被解析为(NOT a) BETWEEN b AND c

执行以下查询

根据解析公式,这里的表达式NOT 1 BETWEEN -5 AND 5被解释为( NOT 1 ) BETWEEN -5 AND 5。

NOT 1的结果是FALSE(在MySQL中表示为0),0 BETWEEN -5 AND 5的结果是TRUE(因为 0 在 -5 和 5 之间),所以最终结果是0。

如果你是从MySQL 5.0.2之前的版本升级到新版本,应考虑是否启用HIGH_NOT_PRECEDENCE参数,启用后会旧老版本运算逻辑优先级保持一致,在生产环境中默认建议关闭HIGH_NOT_PRECEDENCE参数。

IGNORE_SPACE

IGNORE_SPACE参数在默认情况下是禁用的,该参数控制在函数调用时是否允许函数名和左括号之间有空格。启用该模式时,可以在函数名和左括号之间使用空格,而禁用时则不允许。

演示示例

禁用IGNORE_SPACE

创建一个测试表

插入一些测试数据

执行以下查询

报错ERROR 1630 (42000): FUNCTION test_db.COUNT does not exist. Check the 'Function Name Parsing and Resolution' section in the Reference Manual

这是因为,禁用IGNORE_SPACE情况下,COUNT和(之间的空格是不允许的。

正确查询方式如下

执行成功

启用IGNORE_SPACE

执行以下查询

两条sql均执行成功

启用IGNORE_SPACE参数,使函数名和左括号之间的空格成为可选项,但不影响没有空格的情况。因此无论IGNORE_SPACE是否启用,函数名和左括号之间没有空格时,都会被正常解析并执行。建议应遵循没有空格的sql写法,不建议生产环境中启用。

NO_AUTO_VALUE_ON_ZERO

NO_AUTO_VALUE_ON_ZERO参数在默认情况下是禁用的,它影响自增列行为的系统变量。这个参数的主要作用是控制在插入操作中,如何处理自增列的值为零(0)的情况。

注:在使用mysqldump进行逻辑备份时,如果存在自增主键列中有0的值,会存在数据不一致的风险。因此,mysqldump会自动启用NO_AUTO_VALUE_ON_ZERO模式,以避免在备份和还原过程中出现自增主键冲突。

启用时:当插入0到自增列时,系统不会将其视为自动生成的值,而是将0作为一个普通的插入值。

禁用时:当插入0到自增列时,系统会将其视为请求生成一个新的自增值,而不是使用0。

演示示例

禁用NO_AUTO_VALUE_ON_ZERO

创建一个测试表,它包含一个自增列id和一个普通的列name。

插入数据

两条sql执行成功,查询数据

查询后可以看到,两个0被自动转换为下一个可用的自增值(1和2)。

启用NO_AUTO_VALUE_ON_ZERO

插入数据

执行成功

报错ERROR 1062 (23000): Duplicate entry '0' for key 'test_zero_table.PRIMARY'

查询数据

在启用NO_AUTO_VALUE_ON_ZERO时,插入0不会生成自增值,0会被直接插入到自增列中。但是如果已经插入了0,第二次再插入0将会导致主键冲突,出现 "Duplicate entry" 的错误。

在生产环境中,建议根据具体需求决定是否开启NO_AUTO_VALUE_ON_ZERO,建议保持禁用状态。

NO_BACKSLASH_ESCAPES

NO_BACKSLASH_ESCAPES参数在默认情况下是禁用的,它用于控制反斜杠(\)是否被视为转义字符。

启用时,反斜杠会作为普通字符,不会被视为转义字符。

禁用时,反斜杠会被用作转义字符。

演示示例

禁用NO_BACKSLASH_ESCAPES

添加反斜杠查询

反斜杠作为转义字符,因此输出结果中没有反斜杠存在。

启用NO_BACKSLASH_ESCAPES

添加反斜杠查询

反斜杠不会转义,因此输出结果中反斜杠仍然存在。

在生产环境中,建议禁用NO_BACKSLASH_ESCAPES,以保持反斜杠作为转义字符的标准行为,确保字符串处理的兼容性和安全性。

NO_DIR_IN_CREATE

NO_DIR_IN_CREATE参数在默认情况下禁用的,主要用于控制在创建表时是否忽略INDEX DIRECTORY和DATA DIRECTORY指令。这一模式对于复制环境(如主从复制)特别有用,因为在从服务器上通常不需要指定这些目录。

启用时,在创建表时,MySQL会忽略所有的INDEX DIRECTORY和DATA DIRECTORY指令。这意味着无论你在创建表时指定了什么目录,MySQL都会使用默认的存储目录。

禁用时,在创建表时,MySQL将考虑并使用你指定的INDEX DIRECTORY和DATA DIRECTORY。这允许更灵活的存储管理,可以将数据和索引存储在不同的目录中。

在生产环境中,建议使用默认的表存储设置,而不是指定特定的目录。

NO_UNSIGNED_SUBTRACTION

NO_UNSIGNED_SUBTRACTION参数在默认情况下禁用的,用于控制无符号整数之间的减法操作行为。如果在整数之间做减法操作,其中一个类型为UNSIGNED,结果默认为无符号结果,但是该值为负,将报错。

演示示例

禁用NO_UNSIGNED_SUBTRACTION

执行查询

报错ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(cast(0 as unsigned) - 1)'

启用NO_UNSIGNED_SUBTRACTION

执行查询

启用后,无论操作数是有符号还是无符号,减法结果都将视为有符号整数。即使结果为负,MySQL也会返回负值,而不是抛出错误。

如果将减法结果用于更新一个无符号整数列,在启用NO_UNSIGNED_SUBTRACTION的情况下,如果结果为负,则会将其截断为0;如果启用严格模式,则会抛出错误,保持列不变。

在生产环境中,是否启用NO_UNSIGNED_SUBTRACTION应根据你的具体应用场景、数据处理需求和稳定性要求来决定。建议在做出调整前做好充分的评估,默认情况下,建议保持禁用状态。

PAD_CHAR_TO_FULL_LENGTH

PAD_CHAR_TO_FULL_LENGTH参数在默认情况下禁用的,它用于控制在检索CHAR类型列时是否保留尾部空格。

演示示例

禁用PAD_CHAR_TO_FULL_LENGTH

创建测试表

插入测试数据

查询

因为xy的长度为2,所以CHAR_LENGTH(c1)返回2。

启用PAD_CHAR_TO_FULL_LENGTH

查询

'xy'会被填充为'xy '(后面有8个空格),因此CHAR_LENGTH(c1)返回10。

PAD_CHAR_TO_FULL_LENGTH已弃用。预计将在未来的MySQL版本中删除,见MySQL8.4官方文档:https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_pad_char_to_full_length

PIPES_AS_CONCAT

PIPES_AS_CONCAT参数在默认情况下是禁用的,它用于控制SQL查询中管道符”||“操作符的行为。

启用时,||操作符被视为字符串连接操作符(与CONCAT()作用相同)。

禁用时,||操作符被视为逻辑OR操作符。

演示示例

启用PIPES_AS_CONCAT

等同于select CONCAT('Hello' , 'World');

禁用PIPES_AS_CONCAT

禁用时,||将被视为逻辑运算符,而不是字符串连接符。因此,结果将是0或1,而不是'HelloWorld'。

在生产环境中,建议谨慎启用PIPES_AS_CONCAT。更常见的做法是使用CONCAT函数进行字符串连接,以提高代码的可读性和一致性。

REAL_AS_FLOAT

REAL_AS_FLOAT参数在默认情况下是禁用的,它用于控制REAL数据类型的默认行为。

禁用时,REAL数据类型默认被视为DOUBLE。

启用时,REAL数据类型将被视为FLOAT。

演示示例

禁用REAL_AS_FLOAT参数

创建测试表

查看数据类型

可以看到real被视为double

启用REAL_AS_FLOAT参数

创建测试表

查看数据类型

可以看到,启用REAL_AS_FLOAT参数后,real被视为float。

在生产环境中,建议根据应用的精度需求和存储考虑,谨慎选择是否启用REAL_AS_FLOAT参数,如无特殊需求,建议保持禁用状态。

STRICT_ALL_TABLES

STRICT_ALL_TABLES参数与STRICT_TRANS_TABLES参数有些类似,关键区别在于事务表(如InnoDB)和非事务表(如MyISAM)的处理。启用时,会对所有表(包括事务表和非事务表)中的插入和更新操作进行严格检查。而STRICT_TRANS_TABLES仅对事务表中的插入和更新操作进行严格检查。

演示示例参见STRICT_TRANS_TABLES参数。

总体来说,事务表在实际应用中更为常见,因为它们能够更好地满足对数据一致性、安全性和可靠性的需求。而非事务表则在一些特定的、对数据一致性要求不高的场景中仍然可以使用,但其使用频率相对较低。

所以默认情况下STRICT_TRANS_TABLES参数启用,而STRICT_ALL_TABLES禁用。

TIME_TRUNCATE_FRACTIONAL

TIME_TRUNCATE_FRACTIONAL参数在默认情况下是禁用的,它用于控制时间类型值的小数部分,禁用时小数部分四舍五入,启用时会发生截断。

演示示例

禁用TIME_TRUNCATE_FRACTIONAL

创建测试表

插入测试数据

查询结果

可以看到小数部分被四舍五入了

启用TIME_TRUNCATE_FRACTIONAL

插入测试数据

查询结果

可以看到小数部分没有被四舍五入,而是被截断了。

在生产环境中,建议禁用TIME_TRUNCATE_FRACTIONAL,应在明确需要控制时间值的截断行为时才启用它。

附,MySQL my.cnf配置文件生成器:https://dbcnf.wlnmp.com/

原文链接:MySQL sql_mode参数介绍,转载请注明来源!

0