缺陷的背后(三)---mysql之sql_mode為空的陷阱


導語

mysql服務器可以在不同的sql_mode模式下運行,並且可以根據sql_mode系統變量的值,為不同的客戶機應用不同的模式。sql_mode會影響mysql支持的sql語法,並且會執行數據驗證檢查,那不同的mysql_mode是如何影響支持的sql語法和數據類型檢驗的呢?本文將給大家系統總結分析說明這個問題,以及在sql_mode為空的時候,測試需要注意的測試點。

 

目錄 
 一、經典的“測試缺陷” 二、Server SQL Modes 介紹 2.1 sql_mode概念 2.2 七種常見的sql_mode ONLY_FULL_GROUP_BY ANSI_QUOTES PIPES_AS_CONCAT NO_AUTO_CREATE_USER NO_ZERO_DATE NO_ZERO_IN_DATE STRICT_TRANS_TABLES 2.3 三個重要且常用的sql_mode模式 ANSI STRICT_TRANS_TABLES TRADITIONA 三、測試策略 3.1 小結:寬松,嚴格模式下常見sql實例影響和結果對比 3.2 寬松模式下測試應該注意哪些地方?

 

一:經典的“測試缺陷”

     某日版本一如常規的發布上線,灰度過程中開發傳來了一個“噩耗”,程序在操作數據庫時有bug。

       開發:第一筆數據插入都正常,第二筆數據插入就報重入了,報主鍵沖突,而實際這兩筆數據的主鍵應該是不一樣的,后面發現第一筆數據插入時的主鍵值是錯誤導致的。

       測試:怎么會,在日志里打印的每條sql語句我都是有檢查的,測試環境插入多少遍都從沒報過這樣的錯誤,而且每筆數據插入完成后,DB表的主鍵,業務關鍵字段也都是有檢查的,當時我測試的記錄還在,主鍵肯定是插入對的。

       開發:在日志里查看sql語句的主鍵值是對的,但是插入db的主鍵是被截斷的錯誤的。截斷入庫還不報error錯誤,是因為線上mysql的sql_mode為空是寬松模式,導致數據插入時被截斷,也不報error錯誤,業務層會以為是插入成功業務繼續,從而產生了這個問題。

       被截斷的字段是表的主鍵,表的主鍵在DB設計長度是64,在應用程序中,主鍵= 批次號+訂單號,批次號是外部系統返回字段,訂單號由內部產生。測試同學在模塊測試過程中,默認設置批次號為20190717_02,訂單號長度固定為32位,主鍵一直都只有43位,遠小於64位,該問題一直被隱藏;而后跟外部聯調時,實際傳入的批次號是34位,聯調時已經出現主鍵被截斷的錯誤,但是由於設置的sql_mode為空,數據截斷插入不報error錯誤,流程正常運行結束,而且聯調時又一筆通過導致問題在測試階段被雪藏。

二:Server SQL Modes 介紹

2.1 sql_mode概念

sql_mode定義了MySQL應該支持的sql語法,對數據值和類型的校驗程度。不同的sql_mode模式下導致mysql服務器在運行時結果不一樣,有兩種方式可以改變該值:
方法一:靜態修改,修改配置文件(mysql的安裝目錄下my.ini)后重啟mysql。
方法二:動態修改,sql_mode支持全局修改以及會話層修改。

全局查看和全局修改:SELECT @@GLOBAL.sql_mode; SET @@global.sql_mode= 'modes';全局修改就是影響整個數據庫。需要超級特權(root)才能修改,global修改后,需要重新連接進來才會生效。
會話查看和會話修改:SELECT @@SESSION.sql_mode;SET SESSION sql_mode = 'modes';會話就是影響當前連接的會話,如果會話終止,設置的參數值失效。

2.2 sql_mode常用值說明

SQL Mode 定義了兩個方面:MySQL應支持的SQL語法,以及應該在數據上執行何種確認檢查。

2.2.1  常見的SQL語法支持類

    為了更好的舉例說明不同的sql_mode對不同sql的影響,使用表t_payfund_log_20181121,其結構定義如下:

CREATE TABLE `t_payfund_log_20181121` ( `Fbank_seq` varchar(32) NOT NULL, `Flast_bank_seq` varchar(32) DEFAULT NULL, `Fstatus` smallint(6) NOT NULL DEFAULT '0', `Fuid` bigint(20) DEFAULT NULL, `Flast_interface_id` int(11) DEFAULT NULL, PRIMARY KEY (`Fbank_seq`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;

     表內數據暫且包含以下三條記錄:

類型一:ONLY_FULL_GROUP_BY 

   ONLY_FULL_GROUP_BY 對於GROUP BY聚合操作,如果在SELECT中的列、HAVING或者ORDER BY子句的列,沒有在GROUP BY中出現,那么這個SQL是不合法的。是可以理解的,因為不在 group by 的列查出來展示會有矛盾。在5.7中默認啟用,所以DB在從5.6升級到5.7的過程需要注意。

        示例SQL語句:SELECT  Fstatus, Flast_interface_id FROM t_payfund_log_20181121 GROUP BY Fstatus;

        A: sql_mode 為空:查詢結果后正常返回

MySQL [loleina]> SELECT @@GLOBAL.sql_mode;
+-------------------+
| @@GLOBAL.sql_mode |
+-------------------+
|                   |
+-------------------+
1 row in set (0.00 sec)

MySQL [loleina]> SELECT  Fstatus, Flast_interface_id FROM t_payfund_log_20181121 GROUP BY Fstatus;
+---------+--------------------+
| Fstatus | Flast_interface_id |
+---------+--------------------+
|       2 |             998501 |
|       3 |              99802 |
+---------+--------------------+
2 rows in set (0.00 sec)
     B: sql_mode=‘ONLY_FULL_GROUP_BY ':查詢直接報錯   (設置后重新連接登錄)
MySQL [loleina]> SET GLOBAL sql_mode ='ONLY_FULL_GROUP_BY';
Query OK, 0 rows affected (0.00 sec)

MySQL [loleina]> SELECT @@GLOBAL.sql_mode;
+--------------------+
| @@GLOBAL.sql_mode  |
+--------------------+
| ONLY_FULL_GROUP_BY |
+--------------------+
1 row in set (0.00 sec)

MySQL [loleina]> SELECT  Fstatus, Flast_interface_id FROM t_payfund_log_20181121 GROUP BY Fstatus;
ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'loleina.t_payfund_log_20181121.Flast_interface_id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

       當遇到這種情況,可以使用ANY_VALUE(),對於不符合ONLY_FULL_GROUP_BY的字段使用ANY_VALUE()函數,讓MySQL跳過ONLY_FULL_GROUP_BY檢測

MySQL [loleina]> SELECT  Fstatus, ANY_VALUE(Flast_interface_id) FROM t_payfund_log_20181121 GROUP BY Fstatus;
+---------+-------------------------------+
| Fstatus | ANY_VALUE(Flast_interface_id) |
+---------+-------------------------------+
|       2 |                        998501 |
|       3 |                         99802 |
+---------+-------------------------------+
2 rows in set (0.00 sec)

類型二:ANSI_QUOTES

      啟用 ANSI_QUOTES 后,不能用雙引號來引用字符串,因為它被解釋為識別符,作用與 ` 一樣。

      示例SQL語句: update t_payfund_log_20181121 set Flast_bank_seq="20" where Fbank_seq='23020190915090211222002'; 

      A: sql_mode 為空:查詢結果后正常返回.

MySQL [loleina]> SELECT @@GLOBAL.sql_mode; 
+-------------------+
| @@GLOBAL.sql_mode |
+-------------------+
|                   |
+-------------------+
1 row in set (0.00 sec)

MySQL [loleina]> update t_payfund_log_20181121 set Flast_bank_seq="20" where Fbank_seq='23020190915090211222001';    
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

      B: sql_mode=‘ANSI_QUOTES ':報 Unknown column '' in 'field list語法錯誤.

MySQL [loleina]> SELECT @@GLOBAL.sql_mode; 
+-------------------+
| @@GLOBAL.sql_mode |
+-------------------+
| ANSI_QUOTES       |
+-------------------+
1 row in set (0.00 sec)

MySQL [loleina]> update t_payfund_log_20181121 set Flast_bank_seq="20" where Fbank_seq='23020190915090211222002'; 
ERROR 1054 (42S22): Unknown column '20' in 'field list'

類型三:PIPES_AS_CONCAT

         在Oracle,||連接字符串的,而在mysql,是用CONCAT來實現的,mysql為了兼容這一個特性,設置了這個模式,mysql會將 將 || 視為字符串的連接操作符而非 或 運算符,

        示例SQL語句:update t_payfund_log_20181121 set Fmemo=' '||Fmemo||'&pay failed' where Fbank_seq='23020190915090211222002'; 

        等價於: update t_payfund_log_20181121 set Fmemo=CONCAT_WS(' ',Fmemo, '&pay failed') where Fbank_seq='23020190915090211222002'; 

        A: sql_mode 為空:使用||報數據截斷warnings

MySQL [loleina]> SELECT @@GLOBAL.sql_mode; 
+-------------------+
| @@GLOBAL.sql_mode |
+-------------------+
|                   |
+-------------------+
1 row in set (0.00 sec)

MySQL [loleina]>  update t_payfund_log_20181121 set Fmemo=' '||Fmemo||'&pay failed' where Fbank_seq='23020190915090211222002'; 
Query OK, 1 row affected, 2 warnings (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 2

MySQL [loleina]> show warnings;
+---------+------+-------------------------------------------------+
| Level   | Code | Message                                         |
+---------+------+-------------------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: ' test2 '     |
| Warning | 1292 | Truncated incorrect DOUBLE value: '&pay failed' |
+---------+------+-------------------------------------------------+
2 rows in set (0.01 sec)

MySQL [loleina]>  SELECT * from loleina.t_payfund_log_20181121;
+-------------------------+----------------+---------+--------+--------------------+--------------------+
| Fbank_seq               | Flast_bank_seq | Fstatus | Fuid   | Flast_interface_id | Fmemo              |
+-------------------------+----------------+---------+--------+--------------------+--------------------+
| 23020190915090211222001 | 2              |       2 | 540977 |             998501 |  test1 &pay failed |
| 23020190915090211222002 | 2              |       3 |  34566 |              99802 | 0                  |
| 23020190915090211222003 | 3              |       2 |  44322 |             997601 |  test3             |
+-------------------------+----------------+---------+--------+--------------------+--------------------+
3 rows in set (0.00 sec)

        B: sql_mode=‘PIPES_AS_CONCAT  ':數據正常插入。

MySQL [loleina]> SELECT @@GLOBAL.sql_mode; +-------------------+
| @@GLOBAL.sql_mode |
+-------------------+
| PIPES_AS_CONCAT   |
+-------------------+
1 row in set (0.00 sec) MySQL [loleina]>  SELECT * from loleina.t_payfund_log_20181121; +-------------------------+----------------+---------+--------+--------------------+---------+
| Fbank_seq               | Flast_bank_seq | Fstatus | Fuid   | Flast_interface_id | Fmemo   |
+-------------------------+----------------+---------+--------+--------------------+---------+
| 23020190915090211222001 | 2              |       2 | 540977 |             998501 | test1   |
| 23020190915090211222002 | 2              |       3 |  34566 |              99802 |  test2  |
| 23020190915090211222003 | 3              |       2 |  44322 |             997601 |  test3  |
+-------------------------+----------------+---------+--------+--------------------+---------+
3 rows in set (0.00 sec) MySQL [loleina]>     update t_payfund_log_20181121 set Fmemo=' '||Fmemo||'&pay failed' where Fbank_seq='23020190915090211222001'; Query OK, 1 row affected (0.01 sec) Rows matched: 1  Changed: 1  Warnings: 0 MySQL [loleina]>  SELECT * from loleina.t_payfund_log_20181121; +-------------------------+----------------+---------+--------+--------------------+--------------------+
| Fbank_seq               | Flast_bank_seq | Fstatus | Fuid   | Flast_interface_id | Fmemo              |
+-------------------------+----------------+---------+--------+--------------------+--------------------+
| 23020190915090211222001 | 2              |       2 | 540977 |             998501 |  test1 &pay failed |
| 23020190915090211222002 | 2              |       3 |  34566 |              99802 |  test2             |
| 23020190915090211222003 | 3              |       2 |  44322 |             997601 |  test3             |
+-------------------------+----------------+---------+--------+--------------------+--------------------+
3 rows in set (0.00 sec)

類型四:NO_AUTO_CREATE_USER 

      授權命令GRANT 語句的語法:GRANT privileges (columns) ON what TO user IDENTIFIED BY "password" WITH GRANT OPTION,其中IDENTIFIED BY是可選子句,用來於指定mysql用戶的口令。既然是可選,那就是可以沒有。如果沒有指定IDENTIFIED BY,會怎么樣?

      答案是可能導致一個安全漏洞:如果user是指現有用戶,那么沒有任何影響;但如果user是指新用戶,那么該用戶將不被賦予口令,這樣的語句會創建一個口令為空,且已有數據庫操作權限的mysql用戶!那怎么辦呢?mysql自身已經給出了解決方案,將sql_mode設置為NO_AUTO_CREATE_USER,mysql會阻止任何創建空密碼的用戶。

      NO_AUTO_CREATE_USER 字面意思不自動創建用戶。在給MySQL用戶授權時,我們習慣使用上述類似命令 GRANT ... ON ... TO dbuser 一起創建用戶。設置該模式后就與oracle操作類似,授權之前須先建立用戶,否則會報error錯誤。5.7.7開始也默認了。

      示例SQL語句:GRANT ALL PRIVILEGES ON *.* TO 'root'@'10.43.24.149';

       A: sql_mode 為空:存在安全漏洞  

MySQL [loleina]> SELECT @@GLOBAL.sql_mode; 
+-------------------+
| @@GLOBAL.sql_mode |
+-------------------+
|                   |
+-------------------+
1 row in set (0.00 sec)

MySQL [loleina]> GRANT ALL PRIVILEGES ON *.* TO 'root'@'10.43.24.149';
Query OK, 0 rows affected, 1 warning (0.00 sec)

MySQL [loleina]>  GRANT ALL PRIVILEGES ON *.* TO 'root'@'10.43.24.19';
Query OK, 0 rows affected, 1 warning (0.00 sec)

       B: sql_mode=‘NO_AUTO_CREATE_USER ':設置該模式:直接報錯

MySQL [loleina]> SELECT @@GLOBAL.sql_mode; +---------------------+
| @@GLOBAL.sql_mode   |
+---------------------+
| NO_AUTO_CREATE_USER |
+---------------------+
1 row in set (0.00 sec) MySQL [loleina]> GRANT ALL PRIVILEGES ON *.* TO 'root'@'10.43.24.149'; ERROR 1133 (42000): Can't find any matching row in the user table
MySQL [(none)]> GRANT ALL PRIVILEGES ON *.* TO 'root'@'10.43.24.22'; ERROR 1133 (42000): Can't find any matching row in the user table

那如果使用IDENTIFIED BY子句呢?雖然不會直接報error錯誤,但是waring錯誤已經明確給出”不贊成使用grant創建新用戶,會在以后的版本中刪除。使用CREATEUSER語句創建新用戶。”

MySQL [(none)]> GRANT ALL PRIVILEGES ON *.* TO 'root'@'10.43.24.22' IDENTIFIED BY 'root1234' WITH GRANT OPTION; Query OK, 0 rows affected, 1 warning (0.00 sec) MySQL [(none)]> show warnings; +---------+------+------------------------------------------------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                                                            |
+---------+------+------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 1287 | Using GRANT for creating new user is deprecated and will be removed in future release. Create new user with CREATE USER statement. |
+---------+------+------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

2.2.2 數據檢查類

類型五:NO_ZERO_DATE 

       NO_ZERO_DATE 認為日期 '0000-00-00' 是否非法,與是否設置后面的嚴格模式有關。 在嚴格模式,只有‘0000-00-00’報錯,輸入‘1000-00-00’不報錯; 在非嚴格模式,可以接受該‘0000-00-00’但會生成警告。

      示例sql語句:

 INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Fuid`, `Flast_interface_id`,  `Fpay_time`, `Fend_time`) VALUES ('23020190915090211222009',  '540977', '998501', 0000-00-00, 0000-00-00);

       A: sql_mode 為空:'0000-00-00' 能合法插入

MySQL [loleina]> SELECT @@GLOBAL.sql_mode; 
+-------------------+
| @@GLOBAL.sql_mode |
+-------------------+
|                   |
+-------------------+
1 row in set (0.00 sec)


MySQL [loleina]> INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Fuid`, `Flast_interface_id`,  `Fpay_time`, `Fend_time`) VALUES ('23020190915090211222006',  '540977', '998501', 0000-00-00, 0000-00-00);
Query OK, 1 row affected (0.00 sec)

MySQL [loleina]> select * from t_payfund_log_20181121 \g
+-------------------------+----------------+---------+--------+--------------------+--------------------+----------------------------+---------------------+
| Fbank_seq               | Flast_bank_seq | Fstatus | Fuid   | Flast_interface_id | Fmemo              | Fpay_time                  | Fend_time           |
+-------------------------+----------------+---------+--------+--------------------+--------------------+----------------------------+---------------------+
| 23020190915090211222001 | 2              |       2 | 540977 |             998501 |  test1 &pay failed | NULL                       | NULL                |
| 23020190915090211222002 | 2              |       3 |  34566 |              99802 | 0                  | NULL                       | NULL                |
| 23020190915090211222003 | 3              |       2 |  44322 |             997601 |  test3             | NULL                       | NULL                |
| 23020190915090211222006 | NULL           |       0 | 540977 |             998501 | NULL               | 0000-00-00 00:00:00.000000 | 0000-00-00 00:00:00 |
+-------------------------+----------------+---------+--------+--------------------+--------------------+----------------------------+---------------------+
4 rows in set (0.00 sec)

      B: 非嚴格模式設置sql_mode =‘NO_ZERO_DATE’,輸入日期=‘0000-00-00'’ 能插入但會產生告警信息:

MySQL [loleina]> SELECT @@GLOBAL.sql_mode; +-------------------+
| @@GLOBAL.sql_mode |
+-------------------+
| NO_ZERO_DATE      |
+-------------------+
1 row in set (0.00 sec) MySQL [loleina]> INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Fuid`, `Flast_interface_id`,  `Fpay_time`, `Fend_time`) VALUES ('23020190915090211222008',  '540977', '998501', 0000-00-00, 0000-00-00); Query OK, 1 row affected, 2 warnings (0.00 sec) MySQL [loleina]> show warnings; +---------+------+----------------------------------------------------+
| Level   | Code | Message                                            |
+---------+------+----------------------------------------------------+
| Warning | 1264 | Out of range value for column 'Fpay_time' at row 1 |
| Warning | 1264 | Out of range value for column 'Fend_time' at row 1 |
+---------+------+----------------------------------------------------+
2 rows in set (0.00 sec) MySQL [loleina]> select * from t_payfund_log_20181121 \g +-------------------------+----------------+---------+--------+--------------------+--------------------+----------------------------+---------------------+
| Fbank_seq               | Flast_bank_seq | Fstatus | Fuid   | Flast_interface_id | Fmemo              | Fpay_time                  | Fend_time           |
+-------------------------+----------------+---------+--------+--------------------+--------------------+----------------------------+---------------------+
| 23020190915090211222001 | 2              |       2 | 540977 |             998501 |  test1 &pay failed | NULL                       | NULL                |
| 23020190915090211222002 | 2              |       3 |  34566 |              99802 | 0                  | NULL                       | NULL                |
| 23020190915090211222003 | 3              |       2 |  44322 |             997601 |  test3             | NULL                       | NULL                |
| 23020190915090211222006 | NULL           |       0 | 540977 |             998501 | NULL               | 0000-00-00 00:00:00.000000 | 0000-00-00 00:00:00 |
| 23020190915090211222008 | NULL           |       0 | 540977 |             998501 | NULL               | 0000-00-00 00:00:00.000000 | 0000-00-00 00:00:00 |
+-------------------------+----------------+---------+--------+--------------------+--------------------+----------------------------+---------------------+
5 rows in set (0.00 sec)

      C: 嚴格模式下設置sql_mode =‘NO_ZERO_DATE’,輸入日期=‘0000-00-00'’ 插入直接報錯,輸入‘000-00-00’則不報錯

MySQL [loleina]> SELECT @@GLOBAL.sql_mode; +----------------------------------+
| @@GLOBAL.sql_mode                |
+----------------------------------+
| STRICT_TRANS_TABLES,NO_ZERO_DATE |
+----------------------------------+
1 row in set (0.00 sec) MySQL [loleina]> INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Fuid`, `Flast_interface_id`,  `Fpay_time`, `Fend_time`) VALUES ('23020190915090211222009',  '540977', '998501', 0000-00-00, 0000-00-00); ERROR 1292 (22007): Incorrect datetime value: '0' for column 'Fpay_time' at row 1

INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Fuid`, `Flast_interface_id`,  `Fpay_time`, `Fend_time`) VALUES ('23020190915090211222009',  '540977', '998501', 1000-00-00, '1999-01-01 11:11:11');
Query OK, 1 row affected (0.00 sec)

類型六:NO_ZERO_IN_DATE

       NO_ZERO_IN_DATE 認為日期 '0000-00-00' 是否非法,與是否設置后面的嚴格模式有關。 在嚴格模式,只有‘0000-00-00’能正常插入,輸入‘1000-00-00’報錯; 在非嚴格模式,可以接受‘0000-00-00’,但會生成警告。NO_ZERO_IN_DATE :MySQL中插入的時間字段值,支持‘0000-00-00’插入, 但是只要日期的月和日中含有0值就跟嚴格模式設置有關。     
    示例sql語句:
 INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Fuid`, `Flast_interface_id`,  `Fpay_time`, `Fend_time`) VALUES ('23020190915090211222010',  '540977', '998501', 0000-00-00, 0000-00-00);
   A: 非嚴格模式設置sql_mode =‘NO_ZERO_IN_DATE  ’,支持日期=‘0000-00-00',輸入日期=‘1000-00-00'則告警
MySQL [loleina]> SELECT @@GLOBAL.sql_mode; 
+-------------------+
| @@GLOBAL.sql_mode |
+-------------------+
| NO_ZERO_IN_DATE   |
+-------------------+
1 row in set (0.00 sec)


MySQL [loleina]> INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Fuid`, `Flast_interface_id`,  `Fpay_time`, `Fend_time`) VALUES ('23020190915090211222010',  '540977', '998501', 0000-00-00, 0000-00-00);
Query OK, 1 row affected (0.00 sec)

MySQL [loleina]> INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Fuid`, `Flast_interface_id`,  `Fpay_time`, `Fend_time`) VALUES ('23020190915090211222011',  '540977', '998501', 1000-00-00, 0000-00-00);
Query OK, 1 row affected, 1 warning (0.00 sec)

MySQL [loleina]> show warnings;
+---------+------+------------------------------------------------+
| Level   | Code | Message                                        |
+---------+------+------------------------------------------------+
| Warning | 1265 | Data truncated for column 'Fpay_time' at row 1 |
+---------+------+------------------------------------------------+
1 row in set (0.00 sec)


MySQL [loleina]> SELECT * FROM t_payfund_log_20181121 WHERE Fbank_seq = '23020190915090211222012' \g
+-------------------------+----------------+---------+--------+--------------------+-------+----------------------------+---------------------+
| Fbank_seq               | Flast_bank_seq | Fstatus | Fuid   | Flast_interface_id | Fmemo | Fpay_time                  | Fend_time           |
+-------------------------+----------------+---------+--------+--------------------+-------+----------------------------+---------------------+
| 23020190915090211222012 | NULL           |       0 | 540977 |             998501 | NULL  | 0000-00-00 00:00:00.000000 | 0000-00-00 00:00:00 |
+-------------------------+----------------+---------+--------+--------------------+-------+----------------------------+---------------------+
1 row in set (0.00 sec)

       B: 嚴格模式下設置sql_mode =‘NO_ZERO_IN_DATE   ’,輸入日期=‘0000-00-00'’告警,輸入‘1000-00-00’則直接報錯

MySQL [loleina]> SELECT @@GLOBAL.sql_mode; +-------------------------------------+
| @@GLOBAL.sql_mode                   |
+-------------------------------------+
| STRICT_TRANS_TABLES,NO_ZERO_IN_DATE |
+-------------------------------------+
1 row in set (0.00 sec) MySQL [loleina]> INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Fuid`, `Flast_interface_id`,  `Fpay_time`, `Fend_time`) VALUES ('23020190915090211222013',  '540977', '998501', 0000-00-00, 0000-00-00); Query OK, 1 row affected (0.00 sec) MySQL [loleina]> SELECT * FROM t_payfund_log_20181121 WHERE Fbank_seq = '23020190915090211222013' \g +-------------------------+----------------+---------+--------+--------------------+-------+----------------------------+---------------------+
| Fbank_seq               | Flast_bank_seq | Fstatus | Fuid   | Flast_interface_id | Fmemo | Fpay_time                  | Fend_time           |
+-------------------------+----------------+---------+--------+--------------------+-------+----------------------------+---------------------+
| 23020190915090211222013 | NULL           |       0 | 540977 |             998501 | NULL  | 0000-00-00 00:00:00.000000 | 0000-00-00 00:00:00 |
+-------------------------+----------------+---------+--------+--------------------+-------+----------------------------+---------------------+
1 row in set (0.00 sec) MySQL [loleina]> INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Fuid`, `Flast_interface_id`,  `Fpay_time`, `Fend_time`) VALUES ('23020190915090211222013',  '540977', '998501', 1000-00-00, 0000-00-00); ERROR 1292 (22007): Incorrect datetime value: '1000' for column 'Fpay_time' at row 1

類型七:STRICT_TRANS_TABLES  

 嚴格模式,STRICT_TRANS_TABLES 不是幾種策略的組合,單獨指 INSERTUPDATE出現少值或無效值該如何處理,考慮以下5種情況:

       1、整型字段數據超長,DB設置Fstatus長度為6,實際插入數據為12345678

       2、字符串字段數據超長,DB設置Fmemo長度為25,實際插入數據為30.FmemoFmemoFmemoFmemoFmemoFmemo

       3、設置Fstatu的默認值為0,插入時帶這個字段,數據為''

       4、設置Fstatu的默認值為空,插入時不帶這個字段

       5、Fststus字段為int類型,但是插入string類型的數據:abc

   A: sql_mode 為空:報error錯誤:

      1、整型字段長度超長溢出,mysql自動校正為類型最長值插入DB,風險很高

MySQL [loleina]> SELECT @@GLOBAL.sql_mode; 
+-------------------+
| @@GLOBAL.sql_mode |
+-------------------+
|                   |
+-------------------+
1 row in set (0.01 sec)

MySQL [loleina]> INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Flast_bank_seq`, `Fstatus`, `Fuid`, `Flast_interface_id`, `Fmemo`, `Fpay_time`) VALUES ('23020190917090211222001', '3', '12345678', '44322', '997601', 'Fmemo', NULL);
Query OK, 1 row affected, 1 warning (0.00 sec)

MySQL [loleina]> show warnings;
+---------+------+--------------------------------------------------+
| Level   | Code | Message                                          |
+---------+------+--------------------------------------------------+
| Warning | 1264 | Out of range value for column 'Fstatus' at row 1 |
+---------+------+--------------------------------------------------+
1 row in set (0.00 sec)

MySQL [loleina]> select *  from t_payfund_log_20181121 where Fbank_seq='23020190917090211222001' \G
*************************** 1. row ***************************
         Fbank_seq: 23020190917090211222001
    Flast_bank_seq: 3
           Fstatus: 32767
              Fuid: 44322
Flast_interface_id: 997601
             Fmemo: Fmemo
         Fpay_time: NULL
1 row in set (0.00 sec)

      2、字符串字段數據超長被截斷保存到DB

MySQL [loleina]> INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Flast_bank_seq`, `Fstatus`, `Fuid`, `Flast_interface_id`, `Fmemo`, `Fpay_time`) VALUES ('23020190917090211222002', '3', '2', '44322', '997601', 'FmemoFmemoFmemoFmemoFmemoFmemo', NULL);
Query OK, 1 row affected, 1 warning (0.00 sec)

MySQL [loleina]> show warnings;
+---------+------+--------------------------------------------+
| Level   | Code | Message                                    |
+---------+------+--------------------------------------------+
| Warning | 1265 | Data truncated for column 'Fmemo' at row 1 |
+---------+------+--------------------------------------------+
1 row in set (0.00 sec)

MySQL [loleina]> select *  from t_payfund_log_20181121 where Fbank_seq='23020190917090211222002' \G
*************************** 1. row ***************************
         Fbank_seq: 23020190917090211222002
    Flast_bank_seq: 3
           Fstatus: 2
              Fuid: 44322
Flast_interface_id: 997601
             Fmemo: FmemoFmemoFmemoFmemo
         Fpay_time: NULL
1 row in set (0.00 sec)

       3、設置Fstatu的默認值為0,插入時帶這個字段,但數據為'',實際插入為0

MySQL [loleina]> INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Flast_bank_seq`, `Fstatus`, `Fuid`, `Flast_interface_id`, `Fmemo`, `Fpay_time`) VALUES ('23020190917090211222002', '3', '2', '44322', '997601', 'FmemoFmemoFmemoFmemoFmemoFmemo', NULL);
Query OK, 1 row affected, 1 warning (0.00 sec)

MySQL [loleina]> show warnings;
+---------+------+--------------------------------------------+
| Level   | Code | Message                                    |
+---------+------+--------------------------------------------+
| Warning | 1265 | Data truncated for column 'Fmemo' at row 1 |
+---------+------+--------------------------------------------+
1 row in set (0.00 sec)

MySQL [loleina]> select *  from t_payfund_log_20181121 where Fbank_seq='23020190917090211222002' \G
*************************** 1. row ***************************
         Fbank_seq: 23020190917090211222002
    Flast_bank_seq: 3
           Fstatus: 2
              Fuid: 44322
Flast_interface_id: 997601
             Fmemo: FmemoFmemoFmemoFmemo
         Fpay_time: NULL
1 row in set (0.00 sec)

      4、設置Fstatu的默認值為空,插入時不帶這個字段,實際插入0

MySQL [loleina]> INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Flast_bank_seq`, `Fuid`, `Flast_interface_id`, `Fmemo`) VALUES ('23020190917090211222004', '3', '44322', '997601', 'Fmemo');
Query OK, 1 row affected, 1 warning (0.01 sec)

MySQL [loleina]> show warnings;
+---------+------+----------------------------------------------+
| Level   | Code | Message                                      |
+---------+------+----------------------------------------------+
| Warning | 1364 | Field 'Fstatus' doesn't have a default value |
+---------+------+----------------------------------------------+
1 row in set (0.00 sec)

MySQL [loleina]> select *  from t_payfund_log_20181121 where Fbank_seq='23020190917090211222004' \G
*************************** 1. row ***************************
         Fbank_seq: 23020190917090211222004
    Flast_bank_seq: 3
           Fstatus: 0
              Fuid: 44322
Flast_interface_id: 997601
             Fmemo: Fmemo
         Fpay_time: NULL
1 row in set (0.00 sec)

       5、Fststus字段為int類型,但是插入string類型的數據:abc,實際插入為0

MySQL [loleina]> INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Flast_bank_seq`, `Fstatus`, `Fuid`, `Flast_interface_id`, `Fmemo`, `Fpay_time`) VALUES ('23020190916090211222005', '3', 'ABC', '44322', '997601', 'Fmemo', NULL);
Query OK, 1 row affected, 1 warning (0.00 sec)

MySQL [loleina]> show warnings;
+---------+------+--------------------------------------------------------------+
| Level   | Code | Message                                                      |
+---------+------+--------------------------------------------------------------+
| Warning | 1366 | Incorrect integer value: 'ABC' for column 'Fstatus' at row 1 |
+---------+------+--------------------------------------------------------------+
1 row in set (0.00 sec)

MySQL [loleina]> select *  from t_payfund_log_20181121 where Fbank_seq='23020190917090211222005' \G
Empty set (0.00 sec)

MySQL [loleina]> select *  from t_payfund_log_20181121 where Fbank_seq='23020190916090211222005' \G
*************************** 1. row ***************************
         Fbank_seq: 23020190916090211222005
    Flast_bank_seq: 3
           Fstatus: 0
              Fuid: 44322
Flast_interface_id: 997601
             Fmemo: Fmemo
         Fpay_time: NULL
1 row in set (0.00 sec)

B : sql_mode =‘STRICT_TRANS_TABLES ’設置為嚴格模式,1-5插入數據全失敗,報error錯誤:

MySQL [loleina]> SELECT @@GLOBAL.sql_mode; 
+---------------------+
| @@GLOBAL.sql_mode   |
+---------------------+
| STRICT_TRANS_TABLES |
+---------------------+
1 row in set (0.00 sec)


整型超長
MySQL [loleina]> INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Flast_bank_seq`, `Fstatus`, `Fuid`, `Flast_interface_id`, `Fmemo`, `Fpay_time`) VALUES ('23020190916090211222001', '3', '12345678', '44322', '997601', 'Fmemo', NULL);
ERROR 1264 (22003): Out of range value for column 'Fstatus' at row 1

字符串超長
MySQL [loleina]> INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Flast_bank_seq`, `Fstatus`, `Fuid`, `Flast_interface_id`, `Fmemo`, `Fpay_time`) VALUES ('23020190916090211222001', '3', '2', '44322', '997601', 'FmemoFmemoFmemoFmemoFmemoFmemo', NULL);
ERROR 1406 (22001): Data too long for column 'Fmemo' at row 1

設置Fstatu的默認值為0,插入時帶這個字段,但是設置為''
MySQL [loleina]> INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Flast_bank_seq`, `Fstatus`, `Fuid`, `Flast_interface_id`, `Fmemo`, `Fpay_time`) VALUES ('23020190916090211222001', '3', '', '44322', '997601', Fmemo, NULL);
ERROR 1366 (HY000): Incorrect integer value: '' for column 'Fstatus' at row 1

設置Fstatu的默認值為''
MySQL [loleina]> INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Flast_bank_seq`, `Fuid`, `Flast_interface_id`, `Fmemo`) VALUES ('23020190916090211222004', '3', '44322', '997601', 'Fmemo');
ERROR 1364 (HY000): Field 'Fstatus' doesn't have a default value

Fststus為int,但是插入string類型
MySQL [loleina]> INSERT INTO `loleina`.`t_payfund_log_20181121` (`Fbank_seq`, `Flast_bank_seq`, `Fstatus`, `Fuid`, `Flast_interface_id`, `Fmemo`, `Fpay_time`) VALUES ('23020190916090211222005', '3', 'ABC', '44322', '997601', 'Fmemo', NULL);
ERROR 1366 (HY000): Incorrect integer value: 'ABC' for column 'Fstatus'

2.3、三個重要且常用的sql_mode模式

2.3.1ANSI

此模式下,更接近標准sql。包含 REAL_AS_FLOATPIPES_AS_CONCATANSI_QUOTESIGNORE_SPACE, ONLY_FULL_GROUP_BY.

寬松模式,對插入數據進行校驗,如果不符合定義類型或長度,會對數據類型調整或截斷保存,報warning警告。

2.3.2 STRICT_TRANS_TABLES 

     該選項針對事務性存儲引擎生效,對於非事務性存儲引擎無效,該選項表示開啟strict sql模式。在strict sql模式下,在INSERT或者UPDATE語句中,插入或者更新了某個不符合規定的字段值,則會直接報錯中斷操作。包含: ERROR_FOR_DIVISION_BY_ZERONO_ZERO_DATE, and NO_ZERO_IN_DATE modes. 從mysql 5.7.5開始,默認的sql模式包括此模式。

    嚴格模式,進行數據的嚴格校驗,錯誤數據不能插入,報error錯誤。 

2.3.3 TRADITIONAL

        嚴格模式,當向mysql數據庫插入數據時,進行數據的嚴格校驗,保證錯誤數據不能插入,報error錯誤。用於事務時,會進行事物的回滾。 

三:測試策略

3.1 小結:sql_mode=''和其他模式下影響的sql以及結果對比

3.2 寬松模式下測試應該注意哪些地方?

   1、怎么查看出現的warings?

  使用show warnings查看最新warings日志,但在多客戶端使用的情況下,這個日志很快就會被刷新而捕捉不到。  在非嚴格模式下,一些mysql語句會報error錯,這類error錯誤不會記日志,使用show warnings可以看見。show warnings是一個診斷語句,它顯示有關在當前會話中最新的執行語句所導致的條件(錯誤、警告和注釋)的信息

2、可以把現網的sql_mode更改為嚴格模式來防止一些問題的產生嗎?
    實際一般現網運營的DB設置的模式都是嚴格模式,連mysql的5.7以上的版本默認都是嚴格模式,但是可能有些系統運行很久了,在mysql進行升級的時候,可以把sql_mode從原來的寬松模式更改為嚴格模式嗎?
現網是不可以的,mysql官網明確指出:“在創建好表和插入數據到分區表中之后,更改服務器SQL模式可能會導致此類表的行為發生重大變化,並可能導致數據丟失或損壞。強烈建議在使用用戶定義的分區創建表后,不
要更改sql模式。當復制分區表時,主和從機上不同的SQL模式也會導致問題。為了獲得最佳結果,您應該始終在主服務器和從服務器上使用相同的服務器sql模式。”

3、寬松模式下使用mysql數據庫測試需要注意什么呢?

A:注意表的字段的長度和數據類型設計是否合理。因為在長度設計過短或者數據類型設計錯誤時,寬松模式下插入或者更新是不會直接報error錯誤的。
B: 檢查每一筆數據插入時,字段值的正確性。
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM