数据完整性

关系型数据库系统和文件系统的一个不同点是,关系数据库本身能保证存储数据的完整性,不需要应用程序的控制,而文件系统一般需要在程序端进行控制。几乎所有的关系型数据库都提供了约束(constraint)机制,约束提供了一条强大而简易的途径来保证数据库中的数据完整性,数据完整性有三种形式:

  1. 实体完整性 保证表中有一个主键。在InnoDB存储引擎表中,我们可以通过定义Primary Key或者Unique Key约束来保证实体的完整性。或者我们还可以通过编写一个触发器来保证数据完整性。
  2. 域完整性 保证数据的值满足特定的条件。在InnoDB存储引擎表中,域完整性可以通过以下几种途径来保证:选择合适的数据类型可以确保一个数据值满足特定条件,外键(Foreign Key)约束,编写触发器,还可以考虑用DEFAULT约束作为强制域完整性的一个方面。
  3. 参照完整性 保证两张表之间的关系。InnoDB存储引擎支持外键,因此允许用户定义外键以强制参照完整性,也可以通过编写触发器以强制执行。对于InnoDB存储引擎而言,提供了4中约束:Primary Key,Unique Key,Foreign Key,Default,NOT NULL

约束的创建和查找

对于约束的建立,可以在表建立时就进行定义,也可以在之后使用ALTER TABLE命令来进行创建。对于Unique Key的约束,我们还可以通过Create Unique Index来进行建立。对于主键约束而言,其默认约束名为PRIMARY KEY。而对于Unique Key约束而言,默认约束名和列名一样,当然可以人为的指定一个名字。对于Foreign Key约束,似乎会有一个比较神秘的默认名称。下面是一个简单的创建表的语句,表上有一个主键和一个唯一键:

create table u (id int,name varchar(20),id_card char(18),primary key(id),unique key(name));

select constraint_name,constraint_type from information_schema.TABLE_CONSTRAINTS where table_schema='mytest' and table_name='u'\G;

***************************1.row***************************

constraint_name:PRIMARY

constraint_type:PRIMARY KEY

***************************2.row***************************

constraint_name:name

constraint_type:UNIQUE

当然我们还可以通过ALTER TABLE来进行创建,并且可以定义约束的名字,如:

alter table u add unique key uk_id_card(id_card),

select constraint_name,constraint_type from information_schema.TABLE_CONSTRAINTS where table_schema='mytest' and table_name='u'\G;

接着来看Foreign Key的约束,因此我们必须来创建另一张表:

create table p (id int,u_id int,primary key(id),foreign key(u_id) references p(id));

select constraint_name,constraint_type from information_schema.TABLE_CONSTRAINTS where table_schema='mytest' and table_name='u'\G;

***************************1.row***************************

constraint_name:PRIMARY

constraint_type:PRIMARY KEY

***************************2.row***************************

constraint_name:p_ibfk_1

constraint_type:FOREIGN KEY

这里我们通过information_schema架构下的表TABLE_CONSTRAINTS来查看当前MySQL库下所有的约束。对于Foreign Key的约束的定义,我们还可以通过查看表REFERENTIAL_CONSTRAINTS,并且可以详细地了解外键的属性,如:

select * from information_schema.REFERENTIAL_CONSTRAINTS where constraint_schema='mytest'\G;

***************************1.row***************************

CONSTRAINT_CATALOG:NULL

CONSTRAINT_SCHEMA:test2

CONSTRAINT_NAME:p_ibfk_1

UNIQUE_CONSTRAINT_CATALOG:NULL

UNIQUE_CONSTRAINT_SCHEMA:test2

UNIQUE_CONSTRAINT_NAME:PRIMARY

MATCH_OPTION:NONE

UPDATE_RULE:RESTRICT

DELETE_RULE:RESTRICT

TABLE_NAME:p

REFERENCED_TABLE_NAME:p

约束和索引的区别

我们已经看到Primary key和Unique Key的约束。有人不禁会问,这不就是我们创建索引的方法吗?那约束和索引有什么区别呢?的确,当你创建了一个唯一索引,就创建了一个唯一的约束。但是约束和索引的概念还是有所不同的,约束更是一个逻辑的概念,用来保证数据的完整性,而索引是一个数据结构,有逻辑上的概念,在数据库中更是一个物理存储的方式。

对于错误数据的约束

默认情况下,MySQL数据库允许非法或者不正确数据的插入或更新,或者内部将其转化为一个合法的值,如对于NOT NULL的字段插入一个NULL值,会将其更改为0再进行插入,因此本身没有对数据的正确性进行约束。

我们来看一个例子:

create table a (id int not null,date date not null);

insert into a select NULL,'2009-02-30';

show warnings;

***************************1.row***************************

Level:Warning

Code:1048

Message:Column'id'cannot be null

***************************2.row***************************

Level:Warning

Code:1265

Message:Data truncated for column'date' at row 1

select * from a;

+----+-------------+

|id|date

|0|0000-00-00

+----+-------------+

对于NOT NULL的列我插入了一个NULL值,同时插入了一个非法日期'2009-02-30',MySQL都没有报错,而是显示了警告(warning)。如果我们想约束对于非法数据的插入或更新,MySQL是提示报错而不是警告,那么我们应该设置参数sql_mode,用来严格审核输入的参数,如:

set sql_mode='strict_trans_tables';

insert into a select NULL,'2009-02-30';

ERROR 1048(23000):Column'id'cannot be null

insert into a select 1,'2009-02-30';

ERROR 1292(22007):Incorrect date value:'2009-02-30'for column'date'at row 1

我们的目的达到了,这次MySQL约束了输入值的合法性了,而且针对不同的错误,提示的错误内容也都不同。参数sql_mode可设的值有很多,具体的请参考MySQL官方文档。

ENUM和SET约束

MySQL不支持传统的CHECK约束,但是通过ENUM和SET类型可以解决部分这样的约束需求。如我们的表上有一个性别类型,规定域的范围只能是male或者female,这种情况下我们可以通过ENUM类型来进行约束:

create table a (id int,sex enum('male','female'));

insert into a select 1,'female';

insert into a select 2,'bi';

Records:1 Duplicates:0 Warnings:1

可以看到,对于第二条记录的插入依然是抱了警告。因此如果想实现CHECK约束,还需要设置参数sql_mode:

SET sql_mode='STRICT_TRANS_TABLES';

insert into a select 2,'bi';

ERROR 1265(01000):Data truncated for column'sex'at row 1

这次对于非法的输入值进行了约束,但是只限于对离散数值的约束,对于传统CHECK约束支持的连续值的范围约束或者更复杂的约束,ENUM和SET类型还是无能为力,这时我们就需要通过触发器来实现约束了。

触发器与约束

完整性约束通常也可以使用触发器来实现,触发器的作用是在INSERT、DELETE和UPDATE命令之前或之后自动调用SQL命令或者存储过程。MySQL 5.0对于触发器的实现还不是非常完善,限制比较多;而从MySQL 5.1开始,触发器已经相对稳定,功能也较之前有了大幅的提高。

创建触发器的命令是CREATE TRIGGER,只有具备Super权限的MySQL用户才可以执行这条命令:

CREATE

[DEFINER={user|CURRENT_USER}]

TRIGGER trigger_name

BEFORE|AFTER   INSERT|UPDATE|DELETE

ON tbl_name

FOR EACH ROW trigger_stmt

最多可以为一个表建立5个触发器,即分别为INSERT、UPDATE、DELETE的BEFORE和AFTER各定义一个。BEFORE和AFTER代表触发器发生的时间,表示是在每行操作的之前发生还是之后发生。当前MySQL只支持FOR EACH ROW的触发方式,即按每行记录进行触发,不支持如DB2的FOR EACH STATEMENT的触发方式。

通过触发器,我们可以实现MySQL数据库本身并不支持的一些特性,如对于传统CHECK约束的支持、物化视图、高级复制、审计等特性。这里我们先关注触发器对于约束的支持。

我们考虑用户消费表,每次用户购买一样物品后其金额都是减的,若这时有不怀好意的人做了类似减去一个负值的操作,这样的话用户的钱没减少反而会不断地增加。

create table usercash(userid int,cash int unsigned not null);

insert into usercash select 1,1000;

update usercash set cash=cash-(-20) where userid=1;

对于数据库来说,上述的内容没有任何问题,都可以正常运行,不会报错。但是从业务的逻辑上来说,这是错误的,消费总是应该减去一个正值,而不是负值。因此这时如果通过触发器来约束这个逻辑行为的话,可以如下操作:

create table usercash_err_log(

  userid int not null,

  old_cash int unsigned not null,

  new_cash int unsigned not null,

  user varchar(30),

  time datetime);

delimiter$$

create trigger tgr_usercash_update before update on usercash

  for each row

  begin

    if new.cash-old.cash>0 then

      insert into usercash_err_log select old.userid,old.cash,new.cash,user(),now();

      set new.cash=old.cash;

    end if;

  end;

$$

delete from usercash;

insert into usercash select 1,1000;

update usercash set cash=cash-(-20) where userid=1;

select * from usercash;

+--------+-------+

|userid|cash

|1|1000

+--------+-------+

select * from usercash_err_log;

+--------+------------+------------+-------------------

|userid|old_cash|new_cash|user|time

|1|1000|1020|root@localhost|2009-11-06 11:49:49

+--------+------------+------------+------------------

我们创建了一张表用来记录错误数值更新的日志,首先判断新旧值之间的差值,正常情况下消费总是减的,因此新值应该总是小于原来的值,因此对于大于原值的数据,我们判断为非法的输入,将cash值设定为原来的值。

外键

外键用来保证参照完整性,MySQL默认的MyISAM存储引擎本身并不支持外键,对于外键的定义只是起到一个注释的作用。InnoDB存储引擎则完整支持外键约束。外键的定义如下:

[CONSTRAINT[symbol]] FOREIGN KEY

[index_name](index_col_name,……)

REFERENCES tbl_name (index_col_name,……)

[ON DELETE reference_option]

[ON UPDATE reference_option]

reference_option:

RESTRICT|CASCADE|SET NULL|NO ACTION

我们可以在CREATE TABLE时就添加外键,也可以在表创建后通过ALTER TABLE命令来添加。

一个简单的外键的创建示例如下:

CREATE TABLE parent(

  id INT NOT NULL,

  PRIMARY KEY(id)

)ENGINE=INNODB;

CREATE TABLE child(

  id INT,

  parent_id INT,

  index par_ind(parent_id),

  FOREIGN KEY(parent_id) REFERENCES parent(id)

)ENGINE=INNODB;

一般来说,我们称被引用的表为父表,另一个引用的表为子表。外键定义为,ON DELETE和ON UPDATE表示父表做DELETE和UPDATE操作时子表所做的操作。可定义的子表操作有:

  1. CASCADE:当父表发生DELETE或UPDATE操作时,相应的子表中的数据也被DELETE或UPDATE。
  2. SET NULL:当父表发生DELETE或UPDATE操作时,相应的子表中的数据被更新为NULL值。当然,子表中相对应的列必须允许NULL值。
  3. NO ACTION:当父表发生DELETE或UPDATE操作时,抛出错误,不允许这类操作发生。
  4. RESTRICT:当父表发生DELETE或UPDATE操作时,抛出错误,不允许这类操作发生。如果定义外键时没有指定ON DELETE或ON UPDATE,这就是默认的外键设置。在Oracle中,有一种称为延时检查(deferred check)的外键约束,而目前MySQL的约束都是即时检查(immediate check)的,因此从上面的定义可以看出,在MySQL数据库中NO ACTION和RESTRICT的功能是相同的。

在Oracle数据库中,外键通常被人忽视的地方是,对于建立外键的列,一定不要忘记给这个列加上一个索引。而InnoDB存储引擎在外键建立时会自动地对该列加一个索引,这和Microsoft SQL Server数据库的做法一样。因此可以很好地避免外键列上无索引而导致的死锁问题的产生。

对于参照完整性约束,外键能起到一个非常好的作用。但是对于数据的导入操作,外键往往导致大量时间花费在外键约束的检查上,因为MySQL的外键是即时检查的,因此导入的每一行都会进行外键检查。但是我们可以在导入过程中忽视外键的检查,如:

SET foreign_key_checks=0;

LOAD DATA……

SET foreign_key_checks=1;

InnoDB的约束机制的更多相关文章

  1. 巧用MySQL InnoDB引擎锁机制解决死锁问题(转)

    该文会通过一个实际例子中的死锁问题的解决过程,进一步解释innodb的行锁机制 最近,在项目开发过程中,碰到了数据库死锁问题,在解决问题的过程中,笔者对MySQL InnoDB引擎锁机制的理解逐步加深 ...

  2. InnoDB的锁机制浅析(五)—死锁场景(Insert死锁)

    可能的死锁场景 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/插入意 ...

  3. InnoDB的锁机制浅析(四)—不同SQL的加锁状况

    不同SQL的加锁状况 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/ ...

  4. InnoDB的锁机制浅析(三)—幻读

    文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/插入意向锁) Inno ...

  5. InnoDB的锁机制浅析(二)—探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/插入意向锁)

    Record锁/Gap锁/Next-key锁/插入意向锁 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Recor ...

  6. InnoDB的锁机制浅析(一)—基本概念/兼容矩阵

    InnoDB锁的基本概念 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key ...

  7. InnoDB的锁机制浅析(All in One)

    目录 InnoDB的锁机制浅析 1. 前言 2. 锁基本概念 2.1 共享锁和排它锁 2.2 意向锁-Intention Locks 2.3 锁的兼容性 3. InnoDB中的锁 3.1 准备工作 3 ...

  8. InnoDB之锁机制

    前两天听了姜老大关于InnoDB中锁的相关培训,刚好也在看这方面的知识,就顺便利用时间把这部分知识做个整理,方便自己理解.主要分为下面几个部分 1. InnoDB同步机制 InnoDB存储引擎有两种同 ...

  9. 从一个死锁看mysql innodb的锁机制

    背景及现象 线上生产环境在某些时候经常性的出现数据库操作死锁,导致业务人员无法进行操作.经过DBA的分析,是某一张表的insert操 作和delete操作发生了死锁.简单介绍下数据库的情况(因为涉及到 ...

随机推荐

  1. jquery 触屏滑动+定时滚动

    <!doctype html> <html> <head> <meta charset="utf-8"> <meta name ...

  2. Protocol Buffer和JSON性能比较

      JSON PB 数据结构支持 简单结构 较复杂结构 数据格式 文本 二进制 数据大小 一般 小,json大小的1/3左右 解析效率 一般 快,是json解析速度的3-10倍 可读性 好,自描述的 ...

  3. Javascript多线程引擎(三)

    Javascript多线程引擎(三) 完成对ECMAScript-262 3rd规范的阅读后, 列出了如下的限制条件 1. 去除正则表达式( 语法识别先不编写) 2. 去除对Function Decl ...

  4. 在线试听功能(前端直接略过吧,适合javaEE后台开发的)

    应用场景:录音试听,MP3试听... 比如为客户提供录音功能时.客户希望录音完成试听录音,然后下载等功能.直接上代码:关键是取得录音的在服务器的地址,如:url='http://localhost:8 ...

  5. Model Validation(模型验证)

    Model Validation(模型验证) 前言 阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok/p/344 ...

  6. 应用内支付(IAP)可加入三方支付

    Windows Phone 放开政策 - 应用内支付(IAP)可加入三方支付   Windows Phone 应用商店在 今年(2013)11月04号 修改了商店政策 允许公司账户的应用使用三方支付S ...

  7. C# 求精简用一行代码完成的多项判断 重复赋值

    C# 求精简用一行代码完成的多项判断 重复赋值 哈哈,说实话,个人看着这么长的三元操作也麻烦,但是我也只想到了这样三元判断句中执行方法体能够写到一行,追求的终极目的是,用一行实现这个过程,而且简单,由 ...

  8. HDU1423:Greatest Common Increasing Subsequence(LICS)

    Problem Description This is a problem from ZOJ 2432.To make it easyer,you just need output the lengt ...

  9. [整理]在命令行执行 UIAutomation

    instruments -t /Developer/Platforms/iPhoneOS.platform/Developer/Library/Instruments/PlugIns/Automati ...

  10. BF533的SPORT接口

    BF533的SPORT接口 1.特性 bf533有两个SPORT口(synchronous serial Port),即同步串行接口.完全独立的接收和发送通道,且每个通道都具有缓冲,最高速度可达SCL ...