Mysql的外键约束内外连接查询以及锁
Posted 礁之
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mysql的外键约束内外连接查询以及锁相关的知识,希望对你有一定的参考价值。
一、mysql外键约束foreign key
(1)外键约束概述
-
外键约束用来在两个表的数据之间建立连接,它可以是一列或者多列,一个表可以有一个或者多个外键。
-
在设置外键的时候,要有两张表,其中一个是主表,另外一个是从表
-
外键的主要作用就是保持数据的一致性、完整性,能够实验级联删除和级联更新
主表:对于两个具有关联关系的表而言,相关联字段中的主键所在的表就是主表
从表:对于两个具有关联关系的表而言,相关联字段中的外键所在的表就是从表
主表和从表都可以有主键,但是从表的主键不能作为主表的外键
定义外键是由从表定义的,定义自己的某个项去关联某个表的某个项,从而定义外键与主表的主键进行关联
主表删除、修改数据时,从表会进行同步,而主表加数据从表是不会同步的
(2)选取设置Mysql外键约束的字段
定义一个外键时,需要遵守下列规则:
- 主表必须存在于数据库中,或者是当前正在创建的表,如果是第二种情况,则主表和从表是同一个表,这样的表称为自参照表,这种结构称为自参照完整性
- 必须为主表定义主键
- 主键不允许出现空值,但是允许在外键出现空值,也就是说只要外键的每个非空值出现在指定的主键中,那么这个外键的内容就是正确的
- 在主表的表名后面指定列名或列名的组合,这个列或列的组合必须是主表的主键或候选主键
- 外键中的列的数目必须和主表中的主键中的列的数目相同
- 外键中的列的数据类型必须和主表中的主键中的列的数据类型相同
- 存储引擎必须是innoDB,使用mysql5.0以上版本
(3)使用外键约束foreign key
-在创建表时设置外键约束
******(1)进入数据库,创建库和进入库
[root@rzy ~]# mysql -u root -p123123
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \\g.
Your MySQL connection id is 3
Server version: 5.7.12 Source distribution
Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\\h' for help. Type '\\c' to clear the current input statement.
mysql> create database aaa;
Query OK, 1 row affected (0.00 sec)
mysql> use aaa;
Database changed
******(2)创建主表和从表,aaa为主表,bbb为从表,bbb从表的uuid项为aaa主表的外键
mysql> create table aaa(id int primary key,name char(10) not null,age int);
Query OK, 0 rows affected (0.00 sec)
mysql> create table bbb(id int primary key,name char(10),uuid int,constraint waijian foreign key(uuid) references aaa(id));
Query OK, 0 rows affected (0.01 sec)
#constraint :定义外键名称,这里定义的是waijian
#foreign key(uuid) references aaa(id) :定义本表的uuid项为aaa表的外键并且与id项关联
******(3)可以查看两个表的信息,key列中pri为主键,mul为外键
mysql> desc aaa;
+-------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+----------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | char(10) | NO | | NULL | |
| age | int(11) | YES | | NULL | |
+-------+----------+------+-----+---------+-------+
3 rows in set (0.00 sec)
mysql> desc bbb;
+-------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+----------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | char(10) | YES | | NULL | |
| uuid | int(11) | YES | MUL | NULL | |
+-------+----------+------+-----+---------+-------+
3 rows in set (0.00 sec)
-在修改表时添加外键约束
mysql> alter table bbb add constraint waijian foreign key(uuid) references aaa(id); #添加外键约束
Query OK, 1 row affected (0.01 sec)
Records: 1 Duplicates: 0 Warnings: 0
mysql> show create table bbb; #查看bbb表的信息
+-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| bbb | CREATE TABLE `bbb` (
`id` int(11) NOT NULL,
`name` char(10) DEFAULT NULL,
`uuid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `waijian` (`uuid`),
CONSTRAINT `waijian` FOREIGN KEY (`uuid`) REFERENCES `aaa` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
-删除外键约束
mysql> alter table bbb drop foreign key waijian; #删除外键约束,在从表上面删除
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show create table bbb; #查看bbb表的信息
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| bbb | CREATE TABLE `bbb` (
`id` int(11) NOT NULL,
`name` char(10) DEFAULT NULL,
`uuid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `waijian` (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
二、内外连接查询
(1)inner join内连接查询
-
内连接是通过在查询数据时设置连接条件的方式,来移除查询后结果的数据行的交叉连接,利用条件表达式来消除交叉连接的某些数据行
-
在Mysql from子句中使用关键字inner join连接两张表,并且使用on子句来设置连接条件,如果没有任何条件,inner join和cross join 在语法上是等同的,两者可以互换
-
内连接是系统默认的表连接,所有在from子句后面可以省略inner关键字,只用关键字join,使用内连接后,from子句中的on子句可以用来设置连接表的条件
-
在from子句中可以在多个表之间连续使用inner join或join,如此可以同时实现多个表的内连接
mysql> create table aaa(id int,name char (10),age int); #创建两个表
Query OK, 0 rows affected (0.00 sec)
mysql> create table bbb(id int,name char (10),sex char(10));
Query OK, 0 rows affected (0.00 sec)
mysql> insert into aaa values(1,"zhangsan",18); #给两个表插入数据
Query OK, 1 row affected (0.00 sec)
mysql> insert into aaa values(2,"lisi",20);
Query OK, 1 row affected (0.00 sec)
mysql> insert into bbb values(1,"zhangsan","man");
Query OK, 1 row affected (0.00 sec)
mysql> insert into bbb values(2,"lisi","woman");
Query OK, 1 row affected (0.00 sec)
mysql> select * from aaa; #查看两个表的数据
+------+----------+------+
| id | name | age |
+------+----------+------+
| 1 | zhangsan | 18 |
| 2 | lisi | 20 |
+------+----------+------+
2 rows in set (0.00 sec)
mysql> select * from bbb;
+------+----------+-------+
| id | name | sex |
+------+----------+-------+
| 1 | zhangsan | man |
| 2 | lisi | woman |
+------+----------+-------+
2 rows in set (0.00 sec)
mysql> alter table bbb drop name; #先删除bbb表的name项,两个表只要有一个相同项就可以,如果有多个就没必要进行内连接查询了
Query OK, 0 rows affected (0.05 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> select * from bbb;
+------+-------+
| id | sex |
+------+-------+
| 1 | man |
| 2 | woman |
+------+-------+
2 rows in set (0.00 sec)
mysql> select name,sex from aaa,bbb where aaa.id=bbb.id; #查看aaa和bbb表中id项相同的name和sex项的数据
+----------+-------+
| name | sex |
+----------+-------+
| zhangsan | man |
| lisi | woman |
+----------+-------+
2 rows in set (0.00 sec)
mysql> select name,sex from aaa inner join bbb on aaa.id=bbb.id; #和上面查询结果相同,这个使用了inner join内连接
+----------+-------+
| name | sex |
+----------+-------+
| zhangsan | man |
| lisi | woman |
+----------+-------+
2 rows in set (0.00 sec)
(2)left\\right join外连接查询(左连接和右连接)
内连接是交叉显示两个表的数据,而左右连接是将左边或右边的表作为参照表来显示数据
左外链接以左表为主,右外链接以右表为主
mysql> insert into aaa values(3,"hehe",99); #给aaa表再次插入一条数据
Query OK, 1 row affected (0.00 sec)
mysql> select * from aaa; #查看aaa表和bbb表的数据
+------+----------+------+
| id | name | age |
+------+----------+------+
| 1 | zhangsan | 18 |
| 2 | lisi | 20 |
| 3 | hehe | 99 |
+------+----------+------+
3 rows in set (0.00 sec)
mysql> select * from bbb;
+------+-------+
| id | sex |
+------+-------+
| 1 | man |
| 2 | woman |
+------+-------+
2 rows in set (0.00 sec)
mysql> select name,sex from aaa a left outer join bbb b on a.id=b.id; #使用左连接,以左表为参照表,可以看到aaa表中的hehe就算在bbb表中没有,也可以显示,只不过是null的,aaa a表示别名为a,bbb的别名为b
+----------+-------+
| name | sex |
+----------+-------+
| zhangsan | man |
| lisi | woman |
| hehe | NULL |
+----------+-------+
3 rows in set (0.00 sec)
mysql> select name,sex from aaa a right outer join bbb b on a.id=b.id; #和上面大同小异,使用右连接,以右表为参照表,可以看到aaa表的hehe没有了,这是因为bbb表中没有,而bbb表又是参照表所以不会显示
+----------+-------+
| name | sex |
+----------+-------+
| zhangsan | man |
| lisi | woman |
+----------+-------+
2 rows in set (0.00 sec)
三、Mysql的锁
(1)锁的概述
-
MySQL的锁机制,就是数据库为了保证数据的一致性而设计的面对并发场景的一种规则。
-
锁最显著的特点是不同的存储引擎支持不同的锁机制,InnoDB支持行锁和表锁,MyISAM支持表锁。
表锁就是把整张表锁起来,特点是加锁快,开销小,不会出现死锁,锁粒度大,发生锁冲突的概率高,并发相对较低
行锁就是以行为单位把数据锁起来,特点是加锁慢,开销大,会出现死锁,锁粒度小,发生锁冲突的概率低,并发度也相对表锁较高。
死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环
-
行锁增加了系统的开销,要比表锁系统开销大
当事务试图以不同的顺序锁定资源时,就可能产生死锁。多个事务同时锁定同一个资源时也可能会产生死锁
(2)MyISAM的锁调度
- 在MyISAM引擎中,读锁和写锁是互斥的,读写操作是串行的,读锁个写锁都有队列,锁设计方案如下:
对于写操作:如果表上没有锁,则在上面加一把写锁,否则,把请求放到写锁队列中
对于读操作:如果表上没有锁,则在上面加一把读锁,否则,把请求方到读锁队列中
这个意思就是说MyISAM在执行查询语句前,会自动给涉及的所有表加读锁,在执行更新语句(增删改操作)前,会自动给涉及的表加写锁,这个过程并不需要用户干预
- 当一个锁被释放时,锁定权会先被写锁队列中的线程得到,当写锁队列中的请求都跑完后,才轮到读锁队列中的请求。也就是说写锁比读锁的优先级高
即使读请求先到锁等待队列中,写请求后到,写请求也会插入到读请求之前!这就是MySQL认为写请求一般比读请求重要
MyISAM的这种锁调度就意味着,如果一个表上有很多更新操作,那么select语句将等待直到别的更新都结束后才能查到东西。这也就是为什么MyISAM表不适合大量更新操作应用的原因,因为大量更新操作可能导致查询操作很难获得读锁,从而长久阻塞,致使程序响应超时。
(4)表锁语句
MyISAM和InnoDB两种引擎语句都一样
语句 | 作用 |
---|---|
LOCK TABLES 表名称 READ | 加读锁,可读,但不能更新。 |
LOCK TABLES 表名称 WRITE | 加写锁,其他会话不可读,不可写。意思是只有当前给表上锁的会话可以读,除此之外都不行 |
UNLOCK TABLES 表名称 | 释放锁,只能全部解锁,不能指定 |
mysql> lock tables aaa read; #加读锁
Query OK, 0 rows affected (0.00 sec)
mysql> lock tables aaa write; #加写锁
Query OK, 0 rows affected (0.00 sec)
mysql> unlock tables; #解锁
Query OK, 0 rows affected (0.00 sec)
(3)innoDB锁的类型
-共享锁(S锁、读锁)
一个事务获取了一个数据行的读锁,允许其他事务也来获取读锁,但是不允许其他事务来获取写锁。也就是说,一个表上了读锁之后,其他事务也可以来读,但是不能增删改。
-排他锁(X锁、写锁)
一个事务获取了一个数据行的写锁,其他事务就不能再跑来获取任何锁了,所有请求都会被阻塞,直到当前的写锁被释放。
-意向共享锁(IS)
事务在给一个数据行加共享锁之前必须先取得该表的IS锁
-意向排他锁(IX)
事务在给一个数据行加共享锁之前必须先取得该表的IX锁
-MDL锁
在事务中,InnoDB会给涉及的所有表加上一个MDL锁,其他事务就不可以执行任何DDL语句的操作
(只要在事务中,不管是查询语句还是更新语句,涉及到的表都会被加上MDL锁)
这三种锁,都是InnoDB内部使用的锁
(4)行锁的技术
表锁是锁整个表,行锁是锁行数据
-记录锁(record lock)
这是一个索引记录锁,它是建立在索引记录上的锁(主键和唯一索引都算),很多时候,使用行锁去锁一条数据,由于无索引,往往会导致整个表被锁住,建立合适的索引可以防止mysql扫描整个表,从而防止整个表被锁死
如:开两个会话,两个事务,并且都不commit提交,该表有主键,两个会话修改同一条数据,第一个会话update更新执行后,第二个会话的update更新是无法执行成功的,会进入等待状态,但是如果update更新别的数据行就可以成功。
再例如:开两个会话,两个事务,并且都不commit提交,并且该表无主键无索引,那么第二个会话不管改什么都会进入等待状态。因为无索引的话,整个表的数据都被第一个会话锁定了。
-锁等待和死锁
- 锁等待:是指一个事务过程中产生的锁,其他事务需要等待上一个事务释放它的锁,才能占用该资源,如果该事务一直不释放,就需要继续等待下去,直到超过了锁等待时间,会报一个超时错误。
查看锁等待允许时间:
mysql> SHOW VARIABLES LIKE "innodb_lock_wait_timeout";
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 50 |
+--------------------------+-------+
1 row in set (0.00 sec)
- 死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,就是所谓的死循环
**典型的案例就是两个事务并发,同时执行,同时修改自己的一条数据,紧接着又修改对方的锁定的那条数据,都要等待对方的锁,死锁就产生了 **
- 出现死锁的问题并不可怕,解决死锁通常有如下办法:
- 不要把无关的操作放到事务里,小事务发生冲突的概率较低
- 如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样事务就会形成良好的查询并且没有死锁
- 尽量按照索引去查数据,减少发生死锁的可能性,使用范围查找会增加锁冲突的可能性
- 对于非常容易产生死锁的业务部分,可以尝试升级锁粒度,通过表锁定来减少死锁产生的概率
(5)锁的监控
查询哪些表正在被锁定:
mysql> SHOW OPEN TABLES WHERE In_use > 0; #查看哪些表正在被锁定
+----------+-------+--------+-------------+
| Database | Table | In_use | Name_locked |
+----------+-------+--------+-------------+
| aaa | aaa | 1 | 0 |
+----------+-------+--------+-------------+
1 row in set (0.00 sec)
#这个命令监控的是被表锁锁住的表,如果用行锁,这个命令是没有反应的
通过表锁定来减少死锁产生的概率**
(5)锁的监控
查询哪些表正在被锁定:
mysql> SHOW OPEN TABLES WHERE In_use > 0; #查看哪些表正在被锁定
+----------+-------+--------+-------------+
| Database | Table | In_use | Name_locked |
+----------+-------+--------+-------------+
| aaa | aaa | 1 | 0 |
+----------+-------+--------+-------------+
1 row in set (0.00 sec)
#这个命令监控的是被表锁锁住的表,如果用行锁,这个命令是没有反应的
以上是关于Mysql的外键约束内外连接查询以及锁的主要内容,如果未能解决你的问题,请参考以下文章