第04期:列 CHECK 约束

发布时间:2020-08-31 浏览次数:286

check 约束意思,就是对一列或者多列按照一定的预先设置好的规则进行过滤,条件为真,则过滤成功;条件为假,则过滤失败,返回失败代码给客户端。

为什么要把这个单独列出来写呢,经常遇到不规范的 SQL,很多时候只能靠数据库层来过滤,代码端不过滤,就只能在数据库端过滤了。


一、常见过滤方式

假设表 f1,字段 r1 类型为 3 的倍数,否则拒绝写入。又假设 r1 的输入不规范,只能靠数据库来过滤,那怎么办?无非有几种:


1)写前置触发器


示例 1

  1. mysql> create table f1 (r1 int);

  2. Query OK, 0 rows affected (0.03 sec)


  3. DELIMITER $$


  4. USE `ytt`$$


  5. DROP TRIGGER /*!50032 IF EXISTS */ `tr_check_f1_r1`$$


  6. CREATE

  7.    /*!50017 DEFINER = 'root'@'%' */

  8.    TRIGGER `tr_check_f1_r1` BEFORE INSERT ON `f1`

  9.    FOR EACH ROW BEGIN

  10.      IF MOD(new.r1,3) <> 0 THEN

  11.       SIGNAL SQLSTATE '45000'

  12.            SET MESSAGE_TEXT = 'Column r1 should be mod by 3,failed to insert.';

  13.      END IF;

  14.     END;

  15. $$


  16. DELIMITER ;

执行下,暴露出异常

mysql> insert into f1 values (5);ERROR 1644 (45000): Column r1 should be mod by 3,failed to insert.

正常插入

  1. mysql> insert into f1 values (3);

  2. Query OK, 1 row affected (0.01 sec)


  3. mysql> select * from f1;

  4. +------+

  5. | r1   |

  6. +------+

  7. |    3 |

  8. +------+

  9. 1 row in set (0.00 sec)

以上例子简单针对了单列过滤的场景,多列复杂的过滤后面再说。

2)写存储过程封装 SQL

在存储过程里处理输入约束,和在程序端处理输入约束逻辑一致,只是把相同的处理逻辑放在数据库端,并且以后所有对数据的录入只能依赖存储过程单一入口。

3)不拒绝任何输入,定期处理不规范数据

这样会导致录入的数据量非常大,存在很多无用的不规范数据,一般选择非业务高峰时段定期处理不规范数据。

这两种就不举例子了,和第一种类似的处理方法。


二、CHECK 约束

现在要说的是在列这一层次过滤的基于表定义之前就规范好的 CHECK 约束。(MySQL 版本 >= 8.0.16)

  1. mysql> create table f1 (r1 int constraint tb_f1_r1_chk1 check (mod(r1,3)=0));

  2. Query OK, 0 rows affected (0.03 sec)


  3. mysql> create table f2 (r1 int constraint tb_f2_r1_chk1 check (mod(r1,3)=0) not enforced);

  4. Query OK, 0 rows affected (0.02 sec)

这里 CHECK 约束的相关限制如下:

1. constraint 名字在每个数据库中唯一。

也就是说单个数据库里不存在相同的两个 constraint,如果不定义,系统自动生成一个唯一的约束名字。

2. check 约束针对语句 insert/update/replace/load data/load xml 生效;针对对应的 ignore 语句失效。

3. 并非每个函数都可以使用,比如函数结果不确定的:NOW(),CONNECTION_ID(),CURRENT_USER()。

4. 不适用于存储过程和存储函数。

5. 系统变量不适用。

6. 子查询不适用。

7. 外键动作(比如 ON UPDATE, ON DELETE) 不适用。

8. enforced 默认启用,如果单独加上 not enforced ,check 约束失效。


示例 2


结合以上看看刚才那两张表实际的例子,check 约束仅仅对表 f1 生效。

  1. mysql> insert into f1 values (10);

  2. ERROR 3819 (HY000): Check constraint 'tb_f1_r1_chk1' is violated.

  3. mysql> insert into f2 values (10);

  4. Query OK, 1 row affected (0.01 sec)


  5. mysql> select * from f1;

  6. Empty set (0.00 sec)


  7. mysql> select  * from f2;

  8. +------+

  9. | r1   |

  10. +------+

  11. |   10 |

  12. +------+

  13. 1 row in set (0.00 sec)

接下来看看 CHECK 约束更加详细的例子。


示例 3


  1. mysql> drop table f1;

  2. Query OK, 0 rows affected (0.02 sec)


  3. mysql> create table f1

  4.    -> (

  5.    ->  r1 int constraint tb_f1_r1_chk1 check (r1 > 10),

  6.    ->  r2 int constraint tb_f1_r2_positive check (r2 > 0),

  7.    ->  r3 int constraint tb_f1_r3_chk1 check (r3 < 100),

  8.    ->  constraint tb_f1_r1_nonzero check (r1 <> 0),

  9.    ->  constraint tb_f1_r1r2_chk1 check (r1 <> r2),

  10.    ->  constraint tb_f1_r1r3_chk1 check (r1 > r3)

  11.    -> );

  12. Query OK, 0 rows affected (0.02 sec)

上面例子有一点说明下,

1. 约束tb_f1_r1_nonzero、tb_f1_r1r2_chk1、tb_f1_r1r3_chk 不跟随固定的列,对全局有效,也可以说基于表的 check 约束。

2. 约束tb_f1_r1_chk1 包含 约束 tb_f1_r1_nonezero, 这样 tb_f1_r1_nonezero 永远探测不到异常。所以检查后,去掉这个约束。

拿掉多余的约束后的定义,

mysql> create table f1    -> (    ->  r1 int constraint tb_f1_r1_chk1 check (r1 > 10),    ->  r2 int constraint tb_f1_r2_positive check (r2 > 0),    ->  r3 int constraint tb_f1_r3_chk1 check (r3 < 100),    ->  constraint tb_f1_r1r2_chk1 check (r1 <> r2),    ->  constraint tb_f1_r1r3_chk1 check (r1 > r3)    -> );Query OK, 0 rows affected (0.02 sec)

那针对这张表做个测试,可以看到这里每个列的约束其实是“与”的关系,任何一列约束不成立写入就失败。

  1. mysql> insert into f1 values (20,10,10);

  2. Query OK, 1 row affected (0.01 sec)


  3. mysql> insert into f1 values (10,10,10);

  4. ERROR 3819 (HY000): Check constraint 'tb_f1_r1_chk1' is violated.

  5. mysql> insert into f1 values (20,-10,10);

  6. ERROR 3819 (HY000): Check constraint 'tb_f1_r2_positive' is violated.

  7. mysql> insert into f1 values (20,10,30);

  8. ERROR 3819 (HY000): Check constraint 'tb_f1_r1r3_chk1' is violated.

那接下来我们改造刚开始那个触发器,只要把相关条件加进去就可以实现同样的 check 列约束。

  1. DELIMITER $$


  2. USE `ytt`$$


  3. DROP TRIGGER /*!50032 IF EXISTS */ `tr_check_f1_r1`$$


  4. CREATE

  5.    /*!50017 DEFINER = 'root'@'%' */

  6.    TRIGGER `tr_check_f1_r1` BEFORE INSERT ON `f1`

  7.    FOR EACH ROW BEGIN

  8.    DECLARE v1 TINYINT DEFAULT 0;


  9.      IF (new.r1 > 10 AND new.r1 > new.r3 AND new.r1 <> new.r2 AND new.r2 > 0 AND new.r3 < 100) = 0 THEN

  10.        SIGNAL SQLSTATE '45000'

  11.            SET MESSAGE_TEXT = "Failed to write: constraint check: \n (\n r1 >10 \n&&  r1 > r3 \n&& r1 <> r2 \n&& r2> 0 \n&& r3 < 100\n).";

  12.      END IF;

  13.     END;

  14. $$


  15. DELIMITER ;

测试下效果,

  1. mysql> insert into f1 values (20,30,100);

  2. ERROR 1644 (45000): Failed to write: constraint check:

  3. (

  4. r1 >10

  5. &&  r1 > r3

  6. && r1 <> r2

  7. && r2> 0

  8. && r3 < 100

  9. ).


  10. mysql> insert into f1 values (100,30,90);

  11. Query OK, 1 row affected (0.01 sec)


  12. mysql> select * from f1;

  13. +------+------+------+

  14. | r1   | r2   | r3   |

  15. +------+------+------+

  16. |  100 |   30 |   90 |

  17. +------+------+------+

  18. 1 row in set (0.00 sec)


结论

本文介绍了数据库 CHECK 约束相关的用法以及大概例子。

我个人建议,这种 CHECK 约束如果能从数据库端剥离放到应用端实现最好不过了,数据端越简单,性能越好。但也有例外,应用端如果由于历史原因或者其他因素实现困难,也只能寄于数据库端。




上一篇: 新特性解读 | 备份锁

下一篇: 分布式 | DBLE 3.20.07.0 来啦!

咨询客服 在线咨询
400-820-6580 免费电话