最近测试组那边反应数据库部分写入失败,app层提示是插入成功,但表里面里面没有产生数据,而两个写入操作的另外一个表有数据。因为 insert 失败在数据库层面是看不出来的,于是找php的同事看下错误信息:
很明显2个 insert 操作,第一条成功,第二条失败了,但因为没有控制在一个事务当中,导致app里面依然提示成功,这是客户入库操作,心想如果线上也有这个问题得是多大的代价。
不说开发的问题,好端端的mysql怎么突然就部分表写入失败呢?根据上面的问题很快能猜到是 sql_mode 问题: not null 列没有默认值但代码里也没给值,在非严格模式下,int列默认为0,string列默认为''了,所以不成问题;但在严格模式下,是直接返回失败的。
一看,果然:
但是一直是没问题的的,就突然出现了,有谁会去改 sql_mode 呢,生产环境产生这个问题的风险有多大?所以必须揪出来。
先 <code>set global sql_mode=''</code> ,让他们用着先(文后会给解决问题根本的办法),同时打开general_log看是哪一个用户有类似设置 sql_mode 命令:
看出是java组那边哪个框架建立连接的时候使用设置了sql_mode,但这是session级别的,不影响php那边用户的连接。
那会是什么原因在 set global 之后又变回strict模式呢,于是想到 mysqld_safe 启动实际是一个保护进程,在mysqld异常停止之后会拉起来,会不会中间有异常导致 mysqld 重启,致使 global 失效?看了mysql错误日志,才想到前些天断过电,所以决定直接改 <code>/etc/my.cnf</code>配置:
了解一下mysql配置文件的加载顺序:
mysql按照上面的顺序加载配置文件,后面的配置项会覆盖前面的。最后终于在 <code>/usr/my.cnf</code> 找到有一条<code>sql_mode=no_engine_substitution,strict_trans_tables</code>,把这个文件删掉,/etc/my.cnf 里面的就生效了。
但是目前没能整明白的是,mysql运行这么长时间怎么突然在<code>/usr</code> (mysql_base)下多个my.cnf,也不像人为创建的。其它实例也没这样的问题。
类似还出现过一例:存储过程里把 '' 传给int型的,严格模式是不允许,而非严格模式只是一个warning。(命令行执行完语句后,<code>show warnings</code> 可看见)
那么解决这类问题的终极(推荐)办法其实是,考虑到数据的兼容性和准确性,mysql就应该运行在严格模式下!无论开发环境还是生产环境,否则代码移植到线上可能产生隐藏的问题。
sql_mode 问题可以很简单,也可以很复杂。曾经在一个交流群里看到有人提到,主从sql_mode设置不一致导致复制异常,这里自己正好全面了解一下几个常用的值,方便以后排除问题多个方向。
sql语法支持类
<code>only_full_group_by</code>
对于group by聚合操作,如果在select中的列、having或者order by子句的列,没有在group by中出现,那么这个sql是不合法的。是可以理解的,因为不在 group by 的列查出来展示会有矛盾。
在5.7中默认启用,所以在实施5.6升级到5.7的过程需要注意:
<code>ansi_quotes</code>
启用 ansi_quotes 后,不能用双引号来引用字符串,因为它被解释为识别符,作用与 ` 一样。
设置它以后,<code>update t set f1="" ...</code>,会报 unknown column '' in 'field list 这样的语法错误。
<code>pipes_as_concat</code>
将 <code>||</code> 视为字符串的连接操作符而非 或 运算符,这和oracle数据库是一样的,也和字符串的拼接函数 concat() 相类似
<code>no_table_options</code>
使用 <code>show create table</code> 时不会输出mysql特有的语法部分,如 <code>engine</code> ,这个在使用 mysqldump 跨db种类迁移的时候需要考虑。
<code>no_auto_create_user</code>
字面意思不自动创建用户。在给mysql用户授权时,我们习惯使用 <code>grant ... on ... to dbuser</code> 顺道一起创建用户。设置该选项后就与oracle操作类似,授权之前必须先建立用户。5.7.7开始也默认了。
数据检查类
<code>no_zero_date</code>
认为日期 '0000-00-00' 非法,与是否设置后面的严格模式有关。
1.如果设置了严格模式,则 no_zero_date 自然满足。但如果是 insert ignore 或 update ignore,'0000-00-00'依然允许且只显示warning
2.如果在非严格模式下,设置了<code>no_zero_date</code>,效果与上面一样,'0000-00-00'允许但显示warning;如果没有设置<code>no_zero_date</code>,no warning,当做完全合法的值。
3.<code>no_zero_in_date</code>情况与上面类似,不同的是控制日期和天,是否可为 0 ,即 <code>2010-01-00</code> 是否合法。
<code>no_engine_substitution</code>
使用 <code>alter table</code>或<code>create table</code> 指定 engine 时, 需要的存储引擎被禁用或未编译,该如何处理。启用<code>no_engine_substitution</code>时,那么直接抛出错误;不设置此值时,create用默认的存储引擎替代,atler不进行更改,并抛出一个 warning .
<code>strict_trans_tables</code>
设置它,表示启用严格模式。
注意 <code>strict_trans_tables</code> 不是几种策略的组合,单独指 <code>insert</code>、<code>update</code>出现少值或无效值该如何处理:
1.前面提到的把 '' 传给int,严格模式下非法,若启用非严格模式则变成0,产生一个warning
2.out of range,变成插入最大边界值
3.a value is missing when a new row to be inserted does not contain a value for a non-null column that has no explicit default clause in its definition
sql_mode一般来说很少去关注它,没有遇到实际问题之前不会去启停上面的条目。我们常设置的 sql_mode 是 <code>ansi</code>、<code>strict_trans_tables</code>、<code>traditional</code>,ansi和traditional是上面的几种组合。
<code>ansi</code>:更改语法和行为,使其更符合标准sql
相当于real_as_float, pipes_as_concat, ansi_quotes, ignore_space
<code>traditional</code>:更像传统sql数据库系统,该模式的简单描述是当在列中插入不正确的值时“给出错误而不是警告”。
相当于 strict_trans_tables, strict_all_tables, no_zero_in_date, no_zero_date, error_for_division_by_zero, no_auto_create_user, no_engine_substitution
<code>oracle</code>:相当于 pipes_as_concat, ansi_quotes, ignore_space, no_key_options, no_table_options, no_field_options, no_auto_create_user
无论何种mode,产生error之后就意味着单条sql执行失败,对于支持事务的表,则导致当前事务回滚;但如果没有放在事务中执行,或者不支持事务的存储引擎表,则可能导致数据不一致。mysql认为,相比直接报错终止,数据不一致问题更严重。于是 <code>strict_trans_tables</code> 对非事务表依然尽可能的让写入继续,比如给个"最合理"的默认值或截断。而对于 <code>strict_all_tables</code>,如果是单条更新,则不影响,但如果更新的是多条,第一条成功,后面失败则会出现部分更新。
5.6.6 以后版本默认就是<code>no_engine_substitution,strict_trans_tables</code>,5.5默认为 '' 。
查看
设置
配置文件里面设置<code>sql-mode=""</code> 。
<a href="https://dev.mysql.com/doc/refman/5.6/en/sql-mode.html#sql-mode-strict">mysql manual sql-mode</a>
<a href="http://xstarcd.github.io/wiki/mysql/mysql-sql-mode.html">mysql的sql_mode合理设置</a>
<a href="http://dba.stackexchange.com/questions/109053/set-sql-mode-blank-after-upgrading-to-mysql-5-6">set-sql-mode-blank-after-upgrading-to-mysql-5-6</a>
<a href="http://blog.itpub.net/29773961/viewspace-1813501/">mysql sql_mode详解</a>