环境:
参考:《mysql:5.1.11 Server SQL Modes》
先说经验:建议安装mysql实例后将它的运行模式设置为 “TRADITIONAL”。
关于mysql的sql_mode:
mysql为了支持在不同的环境下运行,允许我们给它设置不同的运行模式(sql_mode)。
不同的运行模式,mysql处理我们的sql语句也不相同(mysql提供了18个运行模式,它们可任意组合使用)。
查看和修改sql_mode:
sql_mode
,分为全局的、会话的(即:针对当前链接有效)。
在mysql运行时查看和更改sql_mode的sql如下:
-- 查看当前会话的sql_mode
select @@sql_mode
SELECT @@SESSION.sql_mode;
-- 查看全局的sql_mode
SELECT @@GLOBAL.sql_mode;
-- 设置当前会话的sql_mode
set sql_mode="TRADITIONAL";
SET SESSION sql_mode = 'TRADITIONAL';
-- 设置全局的sql_mode
SET GLOBAL sql_mode = 'TRADITIONAL';
-- 添加 sql_mode
SET SESSION sql_mode = sys.list_add(@@session.sql_mode, 'ONLY_FULL_GROUP_BY');
SET GLOBAL sql_mode = sys.list_add(@@global.sql_mode, 'ONLY_FULL_GROUP_BY');
不过,这样的修改当myql服务器重启时就自动失效了,为了长久有效,建议在 my.ini(window环境是my.ini)文件中进行配置:
mysql的sql_mode简介:
mysql共有18个sql_mode,又有另外的两个组合sql_mode(即:18个sql_mode的其中组合)。
在window下,安装后的默认sql_mode是:STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
。
下面就详细介绍每个sql_mode:
控制是否所有的mysql存储引擎均开启严格模式,默认为OFF,建议ON;
严格模式开启后,mysql会对更改数据的行为做严格的校验,比如:日期的有效性等,一般和其他模式配合使用;
这个也是严格模式,不同的是上面的会对所有存储引擎开启严格模式,而这个会对所有支持事务引擎开启,并尽可能对非事务引擎开启。
默认为ON,建议ON;
控制我们能否向date
、datetime
列插入2020-04-31
这种数据(4月怎么会有31号呢?这是个非法数据)。
当OFF时,不能插入;
当ON时,能插入;
默认为OFF,建议OFF;
是否将双引号也当做对象名称的标识,就像` 一样。
当为OFF时," 用来包裹字符串,不能表示表名和列名等;
当为ON时,"不能包裹字符串,但它可以包裹表名和列名等;
默认为OFF,建议OFF;在mysql中我们可以仅可以用`包裹对象标识名(当然也可以不包裹),单引号包裹字符串。
控制 NOT
运算符是否具有更高的优先级。
当OFF时,select 1 between -5 and 5
等同select not (1 between -5 and 5)
;
当ON时,select 1 between -5 and 5
等同select (not 1) between -5 and 5
;
默认为OFF,建议OFF;
控制是否忽略函数名和(
之间的空格。
当OFF时,函数名和(
之间不能有空格;
当ON时,函数名和(
之间可以有空格;
默认OFF,建议OFF;
这里说下为什么建议OFF而不是ON:
如果为ON的话create table count (l int)
会报错,因为 count 和(
之间的空格会被忽略,而 count(param) 是mysql的内置函数,所以mysql会报语法错误;而为OFF的话,它就不会报错,还是因为它们之间有空格,所以就不会认为它是函数count(param)
。不过,话说回来,我们用关键字做表名、列名的时候自己也会自觉的加上`的吧。当我们使用函数的时候,也不会在函数名和
(
之间故意留空格吧。
所以说,这个按照默认OFF来,我们写sql的时候自己注意:
- 如果用到了mysql的关键字,自觉加`;
- 如果用到函数调用,函数名和
(
之间不要有空格;
控制当把0插入到自增列时是否触发自增。
当为OFF时,将0或null插入到自增列都会触发自增;
当为ON时,将0插入到自增列不会触发自增,因为0会被当做正常数据对待,直接插入进去(插入null依然会触发自增);
默认为OFF,建议OFF;
控制是否将反斜杠识别为转义字符。
当为OFF时,反斜杠是转义字符;
当为ON时,反斜杠就是普通字符,不会对其后的字符转义,此时mysql中也就没有了转移字符;
默认为OFF,建议OFF;
实验:当mysql中的转移字符
drop table if exists test
create table test(
t_name varchar(10)
)
-- 将当前会话的模式清空
set sql_mode=''
-- 此时插入 \n 将被转移为换行符
insert into test(t_name) values('abc\ndef')
-- 将模式改为NO_BACKSLASH_ESCAPES
set sql_mode='NO_BACKSLASH_ESCAPES'
-- 此时插入 \n 将被识别为普通的两个字符
insert into test(t_name) values('abc\ndef')
select * from test
控制是否忽略create table时后面的 DATA DIRECTORY 和 INDEX DIRECTORY 声明。
DATA DIRECTORY 和 INDEX DIRECTORY 声明是当mysql的datedir存储空间不够时允许用户为新建的表指定新的存储地址,如:
CREATE TABLE t_1 engine=innodb DATA DIRECTORY="/data/lottery/" INDEX DIRECTORY="/data/lottery/"
当为OFF时,DATA DIRECTORY 和 INDEX DIRECTORY 继续有效;
当为ON时,DATA DIRECTORY 和 INDEX DIRECTORY 会被忽略;
默认为OFF,建议OFF;
控制是否自动将用户建表时声明的不可用的存储引擎替换成可用的存储引擎。
比如:当我们建表时声明使用某一个存储引擎,但是由于服务器问题,这个引擎暂时不可用,那么此时mysql应该怎么做呢?
当为OFF时,mysql会使用可用的默认殷勤替代;
当为ON时,mysql会直接报错;
默认ON,建议ON;
控制当我们对unsigned int 等做减法时,是否将结果转为对应的非unsigned修饰的类型。
当为OFF时,unsigned int与int相减结果仍为unsigned int;
当为ON时,unsigned int与int相减结果为int;
默认OFF,建议OFF;
解释,为什么建议是OFF而不是ON?
支持为ON的考虑:
- 当为OFF时,如果我们用unsigned int值 3 减去 int 值 5,那么会报错,因为结果-2超出了unsigned int的范围;
但是,作为使用者,我们应该能在使用的时候自行避免这种情况。
支持OFF的考虑:
- 如果为ON的话,那么就强行将unsigned int 的范围缩小了,这样的话还不如直接使用 int好。
所以,建议遵从默认值,设为OFF;
控制是否判别 ‘0000-01-01’(年份为0)为合法的日期,配合StrictMode作用。
当为OFF时, '0000-01-01’是合法日期;
当为ON时, '0000-01-01’不是合法日期;
默认为OFF,建议ON;
关于日期的校验,建议开启严格模式,并严格限制校验插入日期的格式,如下:
set sql_mode="STRICT_ALL_TABLES,STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE"
。
这样的话,才能保证mysql中的日期能正确的读取到c#程序中,否则读取的时候要做各种兼容,因为````‘0000-01-01’```本身就不是正确的日期。可以参考:《mysql:列类型之时间日期》
和NO_ZERO_DATE相似,不同的是前者控制的是年份,后者控制的月份和天(如:2022-13-32)。
默认为OFF,建议ON;
建议设置为:set sql_mode="STRICT_ALL_TABLES,STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE"
控制mysql中 select … group by 语句的写法。
原因是这样的,在sql92标准的时候,规定如果使用了group by子句,那么select子句的列一定被包含在group by 子句中,或者使用sum等聚合函数包裹。
然而,到了sql99的时候,规定select 子句的列也可以不被包含在group by 子句中,如:select t_id,t_name,t_age from test group by t_id
是可以的,因为 t_id 是主键。
看下面的实验:
-- 准备数据
drop table if exists test
create table test(
t_id int primary key,
t_name varchar(50),
t_age int
)
select * from test
insert into test (t_id,t_name,t_age) values(1,'小明',18)
insert into test (t_id,t_name,t_age) values(2,'小王',20)
insert into test (t_id,t_name,t_age) values(3,'小红',20)
-- 将会得到3行,因为 t_id 是主键,分不分组都一样的
select t_id,t_name,t_age from test group by t_id
-- 将会得到两行 为什么第二行是 小王 而不是 小红 呢? 随机的呗
/*
t_id|t_name|t_age|
----+------+-----+
1|小明 | 18|
2|小王 | 20|
*/
select t_id,t_name,t_age from test group by t_age
从上面的实验,可以看到,这样的写法是相当不严谨的。
为了让它严谨一点,我们可以启用ONLY_FULL_GROUP_BY
,当启用后上面第二个查询就会报错了,而第一个查询仍然正常。
而sqlserver对这种情况限制的比较好,它严格限制必须使用group by子句的列或聚合函数。
所以说,ONLY_FULL_GROUP_BY取值是ON或OFF影响并不大,还是在于我们自己怎么写。
综上所述,建议:保持默认 OFF。
控制读取char(M)类型的列值时,当实际值的长度不到M时,是否在默认添加空格以补齐M的长度。
例如,我们向char(10)中插入字符串’abc ‘(abc+3个空格),那么我们读取时:
当为OFF时,读取到的是’abc’,后面没有空格,因为mysql会自动去掉末尾的空格。
当为ON时,读取到的是’abc '(abc+7个空格),mysql会自动补齐到长度10;
默认OFF,建议OFF。另外,这是一个即将被弃用的模式。
注意:sqlsever中对 char(10) 这种定长的字符串也会用右侧的空格补齐。
另外,mysql和sqlserver中均不会对varchar这种变长的字符串右侧补齐。
控制是否将 ‘||’ 作为 string之间的链接符号,就像 concat(string1,string2) 函数一样。
当为OFF时,|| 是 or 的同义词,不过mysql建议使用 or 而不是 || ;
当为ON时,|| 和concat函数类似;
默认OFF,建议OFF;
注意:在mysql中的sql中拼接字符串需要用 concat函数,而不是 “+”,"+" 会将字符串转为数字相加。
控制是否将 real 当做float的同义词,
当为OFF时,real是double的同义词;
当为ON时,real是float的同义词;
默认OFF,建议OFF;
如果我们想定义float或double类型应该会直接写float或double吧?
应该不会故意想用real表示float吧?
所以保持默认即可。
控制当时间的毫秒精度超出时,是进行四设五入,还是直接舍弃多余的位数。
举个例子,当将'14:52:12.15'
插入到time(1)的时候,是插入'14:52:12.2'
还是插入'14:52:12.1'
当为OFF时,进行四设五入,插入14:52:12.2
;
当为ON时,舍弃多余的位数,插入14:52:12.1
;
默认为OFF,建议OFF。
这种模式应该是更贴近sql标准的,它是下面模式的组合:
REAL_AS_FLOAT, PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE, and ONLY_FULL_GROUP_BY.
这种模式是我建议的模式,严谨。
它是下面模式的组合:
STRICT_TRANS_TABLES, STRICT_ALL_TABLES, NO_ZERO_IN_DATE,NO_ZERO_DATE, ERROR_FOR_DIVISION_BY_ZERO, and NO_ENGINE_SUBSTITUTION.