InnoDB的约束机制
数据完整性
关系型数据库系统和文件系统的一个不同点是,关系数据库本身能保证存储数据的完整性,不需要应用程序的控制,而文件系统一般需要在程序端进行控制。几乎所有的关系型数据库都提供了约束(constraint)机制,约束提供了一条强大而简易的途径来保证数据库中的数据完整性,数据完整性有三种形式:
- 实体完整性 保证表中有一个主键。在InnoDB存储引擎表中,我们可以通过定义Primary Key或者Unique Key约束来保证实体的完整性。或者我们还可以通过编写一个触发器来保证数据完整性。
- 域完整性 保证数据的值满足特定的条件。在InnoDB存储引擎表中,域完整性可以通过以下几种途径来保证:选择合适的数据类型可以确保一个数据值满足特定条件,外键(Foreign Key)约束,编写触发器,还可以考虑用DEFAULT约束作为强制域完整性的一个方面。
- 参照完整性 保证两张表之间的关系。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操作时子表所做的操作。可定义的子表操作有:
- CASCADE:当父表发生DELETE或UPDATE操作时,相应的子表中的数据也被DELETE或UPDATE。
- SET NULL:当父表发生DELETE或UPDATE操作时,相应的子表中的数据被更新为NULL值。当然,子表中相对应的列必须允许NULL值。
- NO ACTION:当父表发生DELETE或UPDATE操作时,抛出错误,不允许这类操作发生。
- 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的约束机制的更多相关文章
- 巧用MySQL InnoDB引擎锁机制解决死锁问题(转)
该文会通过一个实际例子中的死锁问题的解决过程,进一步解释innodb的行锁机制 最近,在项目开发过程中,碰到了数据库死锁问题,在解决问题的过程中,笔者对MySQL InnoDB引擎锁机制的理解逐步加深 ...
- InnoDB的锁机制浅析(五)—死锁场景(Insert死锁)
可能的死锁场景 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/插入意 ...
- InnoDB的锁机制浅析(四)—不同SQL的加锁状况
不同SQL的加锁状况 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/ ...
- InnoDB的锁机制浅析(三)—幻读
文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/插入意向锁) Inno ...
- InnoDB的锁机制浅析(二)—探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/插入意向锁)
Record锁/Gap锁/Next-key锁/插入意向锁 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Recor ...
- InnoDB的锁机制浅析(一)—基本概念/兼容矩阵
InnoDB锁的基本概念 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key ...
- InnoDB的锁机制浅析(All in One)
目录 InnoDB的锁机制浅析 1. 前言 2. 锁基本概念 2.1 共享锁和排它锁 2.2 意向锁-Intention Locks 2.3 锁的兼容性 3. InnoDB中的锁 3.1 准备工作 3 ...
- InnoDB之锁机制
前两天听了姜老大关于InnoDB中锁的相关培训,刚好也在看这方面的知识,就顺便利用时间把这部分知识做个整理,方便自己理解.主要分为下面几个部分 1. InnoDB同步机制 InnoDB存储引擎有两种同 ...
- 从一个死锁看mysql innodb的锁机制
背景及现象 线上生产环境在某些时候经常性的出现数据库操作死锁,导致业务人员无法进行操作.经过DBA的分析,是某一张表的insert操 作和delete操作发生了死锁.简单介绍下数据库的情况(因为涉及到 ...
随机推荐
- jquery 触屏滑动+定时滚动
<!doctype html> <html> <head> <meta charset="utf-8"> <meta name ...
- Protocol Buffer和JSON性能比较
JSON PB 数据结构支持 简单结构 较复杂结构 数据格式 文本 二进制 数据大小 一般 小,json大小的1/3左右 解析效率 一般 快,是json解析速度的3-10倍 可读性 好,自描述的 ...
- Javascript多线程引擎(三)
Javascript多线程引擎(三) 完成对ECMAScript-262 3rd规范的阅读后, 列出了如下的限制条件 1. 去除正则表达式( 语法识别先不编写) 2. 去除对Function Decl ...
- 在线试听功能(前端直接略过吧,适合javaEE后台开发的)
应用场景:录音试听,MP3试听... 比如为客户提供录音功能时.客户希望录音完成试听录音,然后下载等功能.直接上代码:关键是取得录音的在服务器的地址,如:url='http://localhost:8 ...
- Model Validation(模型验证)
Model Validation(模型验证) 前言 阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok/p/344 ...
- 应用内支付(IAP)可加入三方支付
Windows Phone 放开政策 - 应用内支付(IAP)可加入三方支付 Windows Phone 应用商店在 今年(2013)11月04号 修改了商店政策 允许公司账户的应用使用三方支付S ...
- C# 求精简用一行代码完成的多项判断 重复赋值
C# 求精简用一行代码完成的多项判断 重复赋值 哈哈,说实话,个人看着这么长的三元操作也麻烦,但是我也只想到了这样三元判断句中执行方法体能够写到一行,追求的终极目的是,用一行实现这个过程,而且简单,由 ...
- 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 ...
- [整理]在命令行执行 UIAutomation
instruments -t /Developer/Platforms/iPhoneOS.platform/Developer/Library/Instruments/PlugIns/Automati ...
- BF533的SPORT接口
BF533的SPORT接口 1.特性 bf533有两个SPORT口(synchronous serial Port),即同步串行接口.完全独立的接收和发送通道,且每个通道都具有缓冲,最高速度可达SCL ...