哎,总是会有些误操作或代码bug(特别是动态SQL),会执行一些无条件(或无有效条件 where 1=1 )更新或删除操作,让人防不胜防,特别是在大表上发生这样的事故的时候,搞得人好心累。
mysql有一个动态变量(sql_safe_updates),可以有效避免(不能完成避免)这种情况的发生。
下面将对此变量进行一些测试:mysql5.7
# 进行测试数据准备
mysql> drop table IF EXISTS test_safe;
Query OK, 0 rows affected (0.01 sec)
mysql> CREATE table IF NOT EXISTS test_safe(
-> id int auto_increment primary key , #主键列
-> vname varchar(32) ,
-> age tinyint default 0,
-> create_time datetime default now(),
-> key idx_vname(vname) #给vname列添加索引
-> );
insert into test_safe(vname,age) values("AAA",1),("BBB",2),("CCC",3),("DDD",4),("EEE",5),("FFF",6),("GGG",7),("HHH",8),("III",9),("JJJ",10);
Query OK, 0 rows affected (0.08 sec)
mysql> insert into test_safe(vname,age) values("AAA",1),("BBB",2),("CCC",3),("DDD",4),("EEE",5),("FFF",6),("GGG",7),("HHH",8),("III",9),("JJJ",10);
Query OK, 10 rows affected (0.00 sec)
Records: 10 Duplicates: 0 Warnings: 0
mysql> desc test_safe;
+-------------+-------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| vname | varchar(32) | YES | MUL | NULL | |
| age | tinyint(4) | YES | | 0 | |
| create_time | datetime | YES | | CURRENT_TIMESTAMP | |
+-------------+-------------+------+-----+-------------------+----------------+
4 rows in set (0.01 sec)
mysql> select * from test_safe;
+----+-------+------+---------------------+
| id | vname | age | create_time |
+----+-------+------+---------------------+
| 1 | AAA | 1 | 2020-11-13 13:38:37 |
| 2 | BBB | 2 | 2020-11-13 13:38:37 |
| 3 | CCC | 3 | 2020-11-13 13:38:37 |
| 4 | DDD | 4 | 2020-11-13 13:38:37 |
| 5 | EEE | 5 | 2020-11-13 13:38:37 |
| 6 | FFF | 6 | 2020-11-13 13:38:37 |
| 7 | GGG | 7 | 2020-11-13 13:38:37 |
| 8 | HHH | 8 | 2020-11-13 13:38:37 |
| 9 | III | 9 | 2020-11-13 13:38:37 |
| 10 | JJJ | 10 | 2020-11-13 13:38:37 |
+----+-------+------+---------------------+
10 rows in set (0.00 sec)
#在全局开启安全更新模式
mysql> set global sql_safe_updates=1;
Query OK, 0 rows affected (0.00 sec)
#在当前会话开启安全更新模式
mysql> set sql_safe_updates=1;
Query OK, 0 rows affected (0.00 sec)
mysql> show global variables like 'sql_safe_updates';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| sql_safe_updates | ON |
+------------------+-------+
1 row in set (0.01 sec)
mysql> show variables like 'sql_safe_updates';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| sql_safe_updates | ON |
+------------------+-------+
1 row in set (0.01 sec)
#测试1:无条件更新。(执行失败)
mysql> UPDATE test_safe SET age = age+1;
ERROR 1175 (HY000): You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column.
#测试2:WHERE 1=1 更新。(执行失败)
mysql> UPDATE test_safe SET age = age+1 WHERE 1=1;
ERROR 1175 (HY000): You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column.
#测试3:根据非索引列条件更新。(执行失败)
mysql> UPDATE test_safe SET age = age+1 WHERE create_time<NOW();
ERROR 1175 (HY000): You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column.
#测试4:根据索引列条件更新。(执行成功)
mysql> UPDATE test_safe SET age = age+1 WHERE vname>'000';
Query OK, 10 rows affected (0.01 sec)
Rows matched: 10 Changed: 10 Warnings: 0
#测试5:limit 更新。(执行成功)
mysql> UPDATE test_safe SET age = age+1 WHERE 1=1 LIMIT 10;
Query OK, 10 rows affected (0.01 sec)
Rows matched: 10 Changed: 10 Warnings: 0
mysql> UPDATE test_safe SET age = age+1 LIMIT 10;
Query OK, 10 rows affected (0.00 sec)
Rows matched: 10 Changed: 10 Warnings: 0
#测试5:like '%XX' 更新。(执行失败)
mysql> UPDATE test_safe SET age = age+1 WHERE vname LIKE '%000';
ERROR 1175 (HY000): You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column.
#测试6:like 'XX%' 更新。(执行成功)
mysql> UPDATE test_safe SET age = age+1 WHERE vname LIKE '000%';
Query OK, 0 rows affected (0.02 sec)
Rows matched: 0 Changed: 0 Warnings: 0
#测试7:无条件删除。(执行失败)
mysql> DELETE FROM test_safe ;
ERROR 1175 (HY000): You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column.
#测试8:where 1=1 删除。(执行失败)
mysql> DELETE FROM test_safe WHERE 1=1;
ERROR 1175 (HY000): You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column.
#测试9:根据非索引列条件删除。(执行失败)
mysql> DELETE FROM test_safe WHERE create_time<NOW();
ERROR 1175 (HY000): You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column.
#测试10:limit 删除。(执行成功)
mysql> DELETE FROM test_safe LIMIT 1;
Query OK, 1 row affected (0.00 sec)
#测试11:根据索引列进行删除。(执行成功)
mysql> DELETE FROM test_safe WHERE vname='CCC';
Query OK, 1 row affected (0.00 sec)
#测试12:根据索引列进行删除。(执行成功)
mysql> DELETE FROM test_safe WHERE vname <> 'CCC';
Query OK, 8 rows affected (0.01 sec)
开启安全更新模式后,update或delete语句必须满足如下条件之一才能执行成功:
1.使用了limit条件
2.where条件中包含索引列,且可以使用到索引。(这个地方不好表述,建议看上面的测试例子进行意会)
3.where条件中不包含索引列,但是使用了limit。
虽然开启安全更新可以避免无条件(where 1=1等)的更新(或删除)操作,但是也不能完成避免全表更新(或删除)操作,像测试4、测试9、测试12中,使用到了索引列,但是给出的条件表中所有的行都满足条件,一样实现了全表更新(或删除)。
注意 : 阿里云 RDS(mysql5.7)不支持此参数,8.0的不知道,没有用过。
个人建议
1.尽量不要使用动态SQL,太复杂的情况判断下,开发者都不知道会生成什么样的SQL。
2.根据主键或索引列进行 等值 的更新或删除操作。
3.加上limit (然后在实际中这个值并不好控制,尴尬)