外键和E.R图
约束管理
- 添加约束
添加普通约束的方式有两种,一种是创建表的时候直接给字段添加相应的约束,另一种是通过修改表的方式添加约束
-- 1. 创建表的时候添加约束
-- 建表的时候可以在字段类型后面加一个或者多个约束
-- 2.通过添加约束索引的方式添加约束
-- alter table 表名 add constraint 索引名 约束(字段);
-- 说明: 索引名 - 自己随便命名; 约束 - 当前想要添加的约束(但是只支持唯一约束、主键约束和外键约束)
-- 示例:
alter table t_teacher add constraint uni_tel UNIQUE(teatel);
- 删除约束
alter table 表名 drop index 约束索引;
-- 示例:
alter table t_teacher drop index uni_tel;
外键约束
- 什么是外键:表中的某个字段的值是根据其他表中主键的值来确定的。那么这个字段就是外键
多对一的外键的添加: 将外键添加到多的一方对应的表中
一对一的外键的添加: 将外键随便添加到哪一方,同时添加值唯一约束
多对多的外键的添加: 关系型数据库中,两张表没法实现多多的关系,需要一个中间表。(中间表有两个外键分别参照多多的两个表的主键)
- 添加外键约束
alter table 表名1 add constraint 外键约束索引名 foreign key (字段1) references 表名2 (字段2);
-- 将表1中的字段1设置为外键,并且让这个外键的值参照表2中的字段2
-- 也可在创建表的时候就添加外键约束
- 删除外键约束
alter table 表名 drop foreign key 外键索引名;
E.R图
E-R图也称实体-联系图(Entity Relationship Diagram),提供了表示实体类型、属性和联系的方法,用来描述现实世界的概念模型。
它是描述现实世界关系概念模型的有效方法。是表示概念关系模型的一种方式。用“矩形框”表示实体型,矩形框内写明实体名称;用“椭圆图框”或圆角矩形表示实体的属性,并用“实心线段”将其与相应关系的“实体型”连接起来。
高级查询
聚合
max() / min() / sum() / avg() / count()
SELECT max(score) as max_score FROM tb_score; -- 获取tb_score表中字段score的最大值
SELECT min(score) as min_score FROM tb_score; -- 获取tb_score表中字段score的最小值
SELECT sum(score) as sum_score FROM tb_score; -- 获取tb_score表中字段score的和
SELECT AVG(score) as avg_score FROM tb_score; -- 获取tb_score表中字段score的平均值
SELECT COUNT(score) as count_score FROM tb_score WHERE score>80; -- 统计tb_score表中字段score大于80的个数
分组
-- SELECT 字段操作 FROM 表名 WHERE 条件 GROUP BY(字段2);
-- 将指定表中满足条件的记录按照字段2的进行分组(值是一样的在一个组里面), 然后再讲每个分组作为整体按照指定字段进行指定聚合操作
-- 注意:a.字段操作的位置除了分组字段不用聚合,其他字段都必须聚合 b.分组的时候where要放到分组前对需要分组的数据进行筛选
select stuid, avg(score) from tb_score group by(stuid);
-- having: 分组的时候,在分组后用having代替where来对分组后的数据进行筛选!!!
select stuid, max(score) from tb_score group by(stuid) having max(score)>90;
select stuid, avg(score) from tb_score group by(stuid) having avg(score)>80;
去重
SELECT DISTINCT addr FROM t_student;
限制和分页
-- 限制: select * from 表名 limit N; - 查询的时候只获取前N条数据
-- 偏移: select * from 表名 limit M offset N; - 跳过前N获取M条数据
select * from tb_record limit 5;
select * from tb_record limit 4 offset 3; -- 跳过前3条获取4条数据
子查询
子查询:将一个查询的结果作为另外一个查询的条件或者查询对象
-- 1.将查询结果作为另外一个查询的条件
-- 获取成绩大于90分的学生姓名
select stuname from tb_student where stuid in
(select stuid from tb_score where score>90);
-- 2.将一个查询的结果作为查询对象提供给另外一个查询。但是第一个查询结果需要重命名
select score from (SELECT stuid,score from tb_score where score>80) as t2;
连接查询
- 直接连接
-- select * from 表名1,表名2,表名3 where 连接条件 查询条件;
-- 查询所有学生的名字和学院名字
select stuname, collname from tb_student, tb_college where tb_student.colid=tb_college.collid;
- 内连接
-- SELECT * FROM 表1 inner join 表2 on 表2的连接条件 inner join 表3 on 表3的连接条件 ...;
-- 查询所有学生的名字和学院名字
select stuname, collname from tb_student inner join tb_college
on tb_student.colid=tb_college.collid;
- 外连接
外连接分为左外连接、右外连接和全连接, 但是在MySQL中支持左外连接和右外连接
-- 左外连接:将左表中对应字段的所有数据取出,然后再对应的右表中字段的值,如果右表对应的值不存在结果就为null
-- 右外连接:将右表中对应字段的所有数据取出,然后再对应的左表中字段的值,如果左表对应的值不存在结果就为null
select * from 表1 left join 表2 on 连接条件;
select * from 表1 right join 表2 on 连接条件;
-- 查询所有学生的姓名和选课数量(左外连接和子查询)
select stuname, ifnull(c_count,0) from tb_student as t1 left join
(select sid, count(cid) as c_count from tb_record group by (sid)) as t2 on stuid=sid;
-- 如果不用外连接,没有选课的学生选不出来!!
事务
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。
一个数据库事务通常包含对数据库进行读或写的一个操作序列。它的存在包含有以下两个目的:
1、为数据库操作提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。
2、当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。
并非任意的对数据库的操作序列都是数据库事务。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
持久性(Durability):一个事务一旦提交,他对数据库的修改应该永久保存在数据库中。
-- 开启事务环境
begin;
-- ...(需要执行的多个操作对应的sql语句)
-- 提交事务(只有begin到commit之间的所有的sql都执行成功,才会执行commit; 否则执行rollback)
COMMIT;
-- 事务回滚(放弃beigin到commit之间执行成功的所有sql语句的结果)
ROLLBACK;
视图
视图是关系型数据库中将一组查询指令构成的结果集组合成可查询的数据表的对象。简单的说,视图就是虚拟的表,但与数据表不同的是,数据表是一种实体结构,而视图是一种虚拟结构,你也可以将视图理解为保存在数据库中被赋予名字的SQL语句。
使用视图可以获得以下好处:
可以将实体数据表隐藏起来,让外部程序无法得知实际的数据结构,让访问者可以使用表的组成部分而不是整个表,降低数据库被攻击的风险。
在大多数的情况下视图是只读的(更新视图的操作通常都有诸多的限制),外部程序无法直接透过视图修改数据。
重用SQL语句,将高度复杂的查询包装在视图表中,直接访问该视图即可取出需要的数据;也可以将视图视为数据表进行连接查询。
视图可以返回与实体数据表不同格式的数据,
- 创建视图
create view 视图名 as sql查询语句;
-- 示例:
create view vw_student
as SELECT * FROM tb_student;
- 使用视图 - 视图在用的时候可以直接当成表来使用
-- 示例:
select * FROM vw_student;
select stuname, collname from vw_student, tb_college where vw_student.colid=tb_college.collid;
3.更新视图 - 更新一个视图将更新其基表
但是并非所有的视图都是可更新的。基本上可以说,如果MySQL不能正确地确定被更新的基数据,则不允许更新(包括插入和删除)。这实际上意味着,如果视图定义中有以下操作,则不能进行视图更新:
- 分组
- 联结
- 子查询
- 并(union)
- 聚合函数
- Distinct
存储过程
如果在实现用户的某些需求时,需要编写一组复杂的SQL语句才能实现的时候,那么我们就可以将这组复杂的SQL语句集提前编写在数据库中,由JDBC调用来执行这组SQL语句。把编写在数据库中的SQL语句集称为存储过程。
使用存储过程的好处:
1.存储过程是通过处理封装在容易使用的单元中,简化了复杂的操作。并且使用存储过程比使用单独的SQL语句要快。
2.存储过程度任何应用程序都是可重用的和透明的。存储过程将数据库接口暴露给所有的应用程序,开发人员可以使用同一存储过程,防止错误,提高安全性。
3.简化对变动的管理。如果表名列名或业务逻辑有变化,只需要更改存储过程的代码。使用它的人员甚至不需要知道这些变化。
话句话说,存储过程的好处主要有3点:简单、安全、高性能。不过它也有一定的缺陷:
1.存储过程的开发和维护都不容易;
2.只有少数数据库系统能调试存储过程,而MySQL数据库不支持调试存储过程;
3.可移植性比较差。
--创建存储过程
create procedure productpricing() -- 括号内可以带参数
begin
select avg(prod_price) as priceaverage from products;
end;
-- 使用存储过程
call productpricing(); -- 可以传参数
-- 删除存储过程
drop procedure productpricing;
索引
索引相当于书本的目录,为表创建索引可以加速查询(用空间换时间)。
索引的优点:
- 索引大大减少了服务器需要扫描的数据量;
- 索引可以帮助服务器避免排序和临时表;
- 索引可以将随机I/O变为顺序I/O。
索引虽然很好,但是不能滥用:
索引会占用额外的空间
索引会让增删改变得更慢
如果哪个列经常被用于查询的筛选条件那么就应该在这个列上建立索引。总地来说,只有当索引帮助存储引擎快速查找到记录带来的好处大于其带来的额外工作时,索引才是有效的。
主键上有默认索引(唯一索引)
- 创建索引
-- create index 索引名 on 表名 (字段); -- 给指定表的指定字段添加索引
-- create unique index 索引名 on 表名 (字段); -- 给指定表的指定字段添加唯一索引
-- 示例:
create index idx_stuname on tb_student(stuname);
create unique index idx_stuname on tb_student(stuname);
- 删除索引
-- alter table 表名 drop index 索引名; -- 删除指定索引,唯一索引也是这样删
-- 示例:
alter table tb_student drop index idx_stuname;
- 执行索引
-- explain: 获取执行计划
explain select * from tb_student where stuid=110;
explain select * from tb_student where stuname='张三';
-- 注意:模糊查询如果以%和_开头,索引无效!!!
explain select * from tb_student where stuname='%三'; -- error
explain select * from tb_student where stuname='_三'; -- error
索引类型
在MySQL中,索引是在存储引擎层而不是服务器层实现的。所以,并没有统一的索引标准:不同存储引擎的索引的工作方式并不一样,也不是所有的存储引擎都支持所有类型的索引。即使多个存储引擎支持同一种索引,其底层的实现也可能不同。
主键索引 PRIMARY KEY
主键索引是一种特殊的索引,我们一般在创建表时同时创建主键,主键是唯一的且不能为空
唯一索引 UNIQUE
唯一索引要求索引的列是唯一的,但可以为空
普通索引 INDEX
普通索引是最基本的索引,它没有任何限制。可以在创建表的时候指定,也可以在之后创建,仅加速查询
组合索引
组合索引,即一个索引包含多个列,专门用于组合搜索,其效率大于索引合并
全文索引 FULLTEXT
全文索引(也称全文检索)是目前搜索引擎使用的一种关键技术。它能够利用分词技术等多种算法智能分析出文本文字中关键字词的频率及重要性,然后按照一定的算法规则智能地筛选出我们想要的搜索结果。
索引结构类型
B-Tree索引
谈论索引时,如果没有特别指明类型,那说的多半是B-Tree索引,它使用B-Tree数据结构来存储数据。大多数MySQL引擎都支持这种索引,Archive引擎是一个例外。
B-Tree索引能够加快访问数据的速度,因为存储引擎不在需要进行全表扫描来获取需要的数据,取而代之的是从索引的根节点开始进行搜索。根节点的槽中存放了指向子节点的指针,存储引擎根据这些指针指向下层查找。通过比较节点页的值和要查找的值可以找到合适的指针进入下层子节点,这些指针实际上定义了子节点页中值的上限和下限。最终存储引擎要么是找到对应的值,要么该记录不存在。
可以使用B-Tree索引的查询类型。B-Tree索引适用于全键值、键值范围或键前缀查找。其中键前缀查找只适用于根据最左前缀的查找。
全值匹配
全值匹配指的是和索引中的所有列进行匹配,例如可用于查找姓名为 Cuba Allen、出生于1980-01-01的人。
匹配最左前缀
前面提到的索引可用于查找所有姓为Allen的人,即只使用索引的第一列。
匹配列前缀
也可以只匹配某一列的值的开头部分。例如索引可用于查找所有以J开头的姓的人。这里也只使用了索引的第一列。
匹配范围值
例如索引可用于查找姓在Allen和Barry之间的人。这里也只使用了索引的第一列。
精确匹配某一列并范围匹配另外一列
索引也可用于查找所有姓为Allen,并且名字是字母K开头(比如Kim、Karl等)的人。即第一列last_name全匹配,第二列first_name范围匹配。
只访问索引的查询
B-Tree通常可以支持“只访问索引的查询”,即查询只需要访问索引,而无须访问数据行。
哈希索引
哈希索引基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码。在MySQL中,只有Memory引擎显式支持哈希索引。这也是Memory引擎表的默认索引类型,Memory引擎同时也支持B-Tree索引。
空间数据索引(R-Tree)
MyISAM表支持空间索引,可以用作地理数据存储。必须使用MySQL的GIS函数来维护数据,但MySQL的GIS支持并不完善,开源关系数据库系统中对GIS的解决方案做的比较好的是PostgreSQL的PostGIS。
全文索引 FULLTEXT
目前只有MyISAM引擎支持。其可以在CREATE TABLE ,ALTER TABLE ,CREATE INDEX 使用,不过目前只有 CHAR、VARCHAR ,TEXT 列上可以创建全文索引。
索引优化(创建索引策略)
1.对维度高(数据列不重复出现的数量越多,维度就越高)的列创建索引;
2.对 where...on..., group by, order by 中出现的列使用索引;
3.对较小的数据列使用索引,这样会使索引文件更小,同时内存中也可以装载更多的索引键;
4.为较长的字符串使用前缀索引;
5.不要过多创建索引,除了增加额外的磁盘空间外,对于DML操作的速度影响很大,因为其每增删改一次就得从新建立索引;
6.使用组合索引,可以减少文件索引大小,在使用时速度要优于多个单列索引;
7.不要在索引列上加函数或者运算,这样不会使用索引;
8.避免前导模糊查询,如select * from user where name like '%明'
不适用索引;
9.少使用负向条件(!=、<>、not in、not like、not exists...),因为不会使用索引(不绝对,有时会走范围索引,取决于范围大小);
10....
索引失效
索引失效的几种情况:
1.当使用左或者左右模糊匹配的时候,比如 like %明 或者 like %明% 这两种方式都会造成索引失效。
2.当我们在查询条件中对索引列使用聚合函数或者进行表达式计算,也是无法走索引的。
3.对索引隐式类型转换
MySQL 在遇到字符串和数字比较的时候,会自动把字符串转为数字,然后再进行比较。如果字符串是索引列,而条件语句中的输入参数是数字的话,那么索引列会发生隐式类型转换,由于隐式类型转换是通过 CAST 函数实现的,等同于对索引列使用了函数,所以就会导致索引失效。
4.联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配,否则就会导致索引失效。
5.在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效。
数据库设计原则(MySQL)
1.一般情况下,应该尽量使用可以正确存储数据的最小数据类型。数据类型不一样,存储的执行效率也不一样。最好使用适度的整型数据类型,例如int之类的数据,这样在做查询或者字段排序的时候速度是最快的。
2.尽量避免NULL值的时候,因为这样会增加数据库处理的开销。但是也要考虑实际情况,不要一味的为了避免空值而全部设置为not null,具体的设置情况要根据项目的具体业务来。
3.注意char和varchar的使用,char适合存储的大小基本固定在一个范围之内,经常发生变动的数据。而varchar则不一样,varchar适合那种大小不固定,并未经常发生改动的数据。需要注意的是varchar定义的长度最好可以刚好够用,不然会照成资源的浪费,影响整体数据库的性能和存储空间。
4.能用整数标示,最好利用整数标示,因为这样开销最小,效率也是最高的。
5.适当使用索引,使用索引会大大提升查询效率,同时降低在被索引的表上INSERT和DELETE效率;
6.在设计表和设计查询语言的时候就要主要尽量避免大规模的关联查询,因为这样会严重影响数据库的查询效率。
7.在设计数据库的时候要根据具体的业务设计出合理的数据库表和字段。不仅要考虑范式,也要考虑反范式。在不影响数据安全和数据冗余的情况下,可以适当的考虑混合范式的设计。
第一范式:属性具有原子性,不可再分解,即不能表中有表;
第二范式:唯一性约束,每条记录有唯一标示,所有的非主键字段均需依赖于主键字段;
第三范式:冗余性约束,非主键字段间不能相互依赖;
8.使用缓存表和汇总表进行数据库的查询优化。
9.大型数据库:
(1)数据分离:长文本短文本分离, 长文本存储在k-v系统中;当前数据与历史数据分离。
(2)负载均衡;
(3)分布式存储。
数据库优化
数据库优化可以分为架构优化、硬件优化、DB优化、SQL优化。
架构优化
一般来说在高并发的场景下对架构层进行优化其效果最为明显,常见的优化手段有:分布式缓存,读写分离,分库分表等,每种优化手段又适用于不同的应用场景。
分布式缓存
有句老话说的好,性能不够,缓存来凑。当需要在架构层进行优化时我们第一时间就会想到缓存这个神器,在应用与数据库之间增加一个缓存服务,如Redis或Memcache。
当接收到查询请求后,我们先查询缓存,判断缓存中是否有数据,有数据就直接返回给应用,如若没有再查询数据库,并加载到缓存中,这样就大大减少了对数据库的访问次数,自然而然也提高了数据库性能。
不过需要注意的是,引入分布式缓存后系统需要考虑如何应对缓存穿透、缓存击穿和缓存雪崩的问题。
读写分离
一主多从,读写分离,主动同步,是一种常见的数据库架构优化手段。
一般来说当你的应用是读多写少,数据库扛不住读压力的时候,采用读写分离,通过增加从库数量可以线性提升系统读性能。
主库,提供数据库写服务;从库,提供数据库读能力;主从之间,通过binlog同步数据。
分库分表
分库分表就是要将大量数据分散到多个数据库和数据表中,使每个数据库中数据量小响应速度快,以此来提升数据库整体性能。
当应用业务数据量很大,单库容量成为性能瓶颈后,采用水平切分,可以降低数据库单库容量,提升数据库写性能。
根据业务耦合性,将关联度低的不同表存储在不同的数据库。垂直切分,做法与大系统拆分为多个小系统类似,按业务分类进行独立划分。与"微服务治理"的做法相似,每个微服务使用单独的一个数据库。
硬件优化
我们使用数据库,不管是读操作还是写操作,最终都是要访问磁盘,所以说磁盘的性能决定了数据库的性能。一块PCIE固态硬盘的性能是普通机械硬盘的几十倍不止。
DB优化
SQL执行慢有时候不一定完全是SQL问题,手动安装一台数据库而不做任何参数调整,再怎么优化SQL都无法让其性能最大化。要让一台数据库实例完全发挥其性能,首先我们就得先优化数据库的实例参数。
数据库实例参数优化遵循三句口诀:日志不能小、缓存足够大、连接要够用。
SQL优化
SQL优化流程:
1.查看执行计划 explain sql
2.如果有告警信息,查看告警信息 show warnings;
3.查看SQL涉及的表结构和索引信息
4.根据执行计划,思考可能的优化点
5.按照可能的优化点执行表结构变更、增加索引、SQL改写等操作
6.查看优化后的执行时间和执行计划
7.如果优化效果不明显,重复第四步操作
SQL优化方法:
1.避免使用select *,select *不走覆盖索引,会出现大量的回表操作,从而导致SQL的性能很低。
2.使用union all 代替union,因为union去重的过程需要遍历,排序和比较,更加消耗资源和时间。
3.小表驱动大表,in和exists的核心思想就是小表驱动大表
-- 假如有order和user两张表,其中order表有10000条数据,而user表有100条数据。这时如果想查一下,所有有效的用户下过的订单列表。
select * from order where user_id in (select id from user where status=1)
select * from order where exists (select 1 from user where order.user_id = user.id and status=1)
-- 前面提到的这种业务场景,使用in关键字去实现业务需求,更加合适。
-- 因为如果sql语句中包含了in关键字,则它会优先执行in里面的子查询语句,然后再执行in外面的语句。
-- 如果in里面的数据量很少,作为条件查询速度更快。
-- 而如果sql语句中包含了exists关键字,它优先执行exists左边的语句(即主查询语句)。
4.批量操作
-- 逐条插入
insert into order(id,code,user_id) values(123,'001',100);
-- 批量插入
insert into order(id,code,user_id) values(123,'001',100),(124,'002',100),(125,'003',101);
-- 这样只需要远程请求一次数据库,sql性能会得到提升,数据量越多,提升越大。
-- 但不建议一次批量操作太多的数据,建议每批数据尽量控制在500以内。如果数据多于500,则分多批次处理。
5.增量查询
-- 如果数据很多的话,性能会很差;
select * from user;
-- 按id和时间升序,分批次查询,每次只查100条记录。通过这种增量查询的方式,能够提升单次查询的效率。
select * from user
where id>#{lastId} and create_time >= #{lastCreateTime}
limit 100;
6.连接查询代替子查询
mysql执行子查询时,需要创建临时表,查询完毕后,需要再删除这些临时表,有一些额外的性能消耗。
select * from order
where user_id in (select id from user where status=1);
select o.* from order o
inner join user u on o.user_id = u.id
where u.status=1
7.join的表不宜超过3个
如果join太多,mysql在选择索引的时候会非常复杂,很容易选错索引。
8.使用left join关联查询时,左边要用小表,右边可以用大表。如果能用inner join的地方,尽量少用left join。
9.索引优化,注意有时SQL语句会导致不走索引
10.提升group by的效率
使用where条件在分组前,就把多余的数据过滤掉了,这样分组时效率就会更高一些。
其实这是一种思路,不仅限于group by的优化。我们的sql语句在做一些耗时的操作之前,应尽可能缩小数据范围,这样能提升sql整体的性能。