候选码:能唯一的标示一个元组,若元组中有很多候选码,选一个为主码(候选码的属性称为主属性)
1NF强调列的原子性,表中的列不能再分,不能表中有表。
2NF基于1NF之上,表中必须又一个主属性,每个非主属性完全依赖于主属性,而不能只依赖一其中部分。
学号 系名 宿舍楼 课程 分数 主键 (学号 课程)
1 通院 16# python 90
系名部分依赖于(学号 课程) <知道学号 或者 课程就能确定系名>
宿舍楼部分依赖于(学号 课程) 不完全依赖于候选码 不属于2NF
3NF基于2NF之上,非主属性必须直接依赖于主属性,不能出现传递依赖,
A 主键 B 非主键 C非主键
C依赖于B,B依赖于A
不符合3NF:
学号 姓名 年龄 学院 学院电话 学院长
(学院长 学院电话)-----> 学院 -------> 学号
符合:
学号 姓名 年龄 学院
学院 学院长 学院电话
BC范式 表里除了主键,其他的列不能存在唯一信息 (下面不符合,除了学号,邮箱也能唯一标示一行)
学号 姓名 邮箱
1 张三 [email protected]
2 李四 [email protected]
5 张三 [email protected]
4NF 表里不能有多值依赖,
学号 姓名 语言
1 张三 英语、汉语、韩语
符合
学号 姓名 语言
1 张三 英语
学号 姓名 语言
1 张三 汉语
学号 姓名 语言
1 张三 汉语
总结
1NF不能表中有表
2NF非主键不能依赖主键中的一部分
3NF非主键不能依赖非主键
BCNF每一行只能有一个唯一标识属性
4NF一列中不能有多值
创建表
CREATE TABLE `userprofile` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(10) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`department_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
constraint `fk` foreign key (`department_id`) references `department`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
清空表
truncate table userprofile; 可以删除自增id和数据,整体删除不写log
delete from userprofile; 逐条删,写log,速度慢
删除表
drop table userprofile;
查看表结构
desc userprofile;
查看创建语句,\G 竖行表示
show create table userprofile \G;
查看函数创建语句
show create function function_name;
查看存储过程创建语句
show create procedure proce_name;
创建root用户远程
create user 'root'@'%' indentified by '123456';
grant all privileges on *.* to 'root'@'%';
flush privileges;
主键约束:primary key 唯一 非空
外键约束:foreign key 另一个表的主键,值只能在里面选
唯一约束:unique key 唯一
非空约束:not null 不为空
限值
limit 10 取十条
limit 10, 20 从11条开始 取20条
偏移
limit 10 offset 20 从21条开始,取10条
排序
order by asc 顺序
desc 逆序
分组
group by
聚合函数
最大值 max
最小值 min
求和 sum
求平均 avg
求数量 count
select count(id), max(id) as max_id, department_id from userprofile group by department_id order by max_id desc;
连表
left join 左连接
select * from user left join department on user.department_id=department.id;
显示user左边的数据 右边的表没数据为NULL
select * from department left join user on user.department_id=department.id;
显示department左边数据
right join 右连接
显示右边的数据 左边的表没数据为NULL
inner join 内连接
将null的行隐藏
从一个或多个基本表导出的表。它本身不存储在数据库中,
只存放视图的定义,不存放视图对应的数据,视图是一个虚拟临时表,不能修改,开发人员不建议使用
CREATE VIEW `view_userprofile` AS(SELECT * FROM userprofile WHERE id>2)
为什么使用视图
1、重复SQL语句
2、将复杂得SQL操作,转换成虚拟表
3、保护数据,可以将表得特定部分授权,而不是整个表
视图得特点
1、不能索引,不能有关联的触发器和默认值
2、可以和表一起使用
3、不能修改
触发器是一种特殊的存储过程,但是不会主动执行,满足条件时执行触发器中定义的sql语句,视图不支持
# 插入前
CREATE TRIGGER `trigger_name` BEFORE INSERT ON `table_name` FOR EACH ROW
BEGIN
END
# 插入后
CREATE TRIGGER `trigger_name` AFTER INSERT ON `table_name` FOR EACH ROW
BEGIN
...
END
# 删除前
CREATE TRIGGER `trigger_name` BEFORE DELETE ON `table_name` FOR EACH ROW
BEGIN
...
END
# 删除后
CREATE TRIGGER `trigger_name` AFTER DELETE ON `table_name` FOR EACH ROW
BEGIN
...
END
# 更新前
CREATE TRIGGER `trigger_name` BEFORE UPDATE ON `table_name` FOR EACH ROW
BEGIN
...
END
# 更新后
CREATE TRIGGER `trigger_name` AFTER UPDATE ON `table_name` FOR EACH ROW
BEGIN
...
END
其中
NEW表示即将插入的数据行,插入、更新
OLD表示即将删除的数据行,更新、删除
创建触发器
DELIMITER // # 修改结束符
在 userprofile表中插入数据之前触发
CREATE TRIGGER `update_record` BEFORE INSERT ON `userprofile` FOR EACH ROW
begin
insert into update_log(record_name, update_time) values(NEW.name, NOW());
end
//
DELIMITER ; # 还原结束符
删除触发器
DROP TRIGGER update_record;
为什么要用存储过程
SQL语句的集合:
缺点:
创建
delimiter //
create procedure p1()
BEGIN
SELECT * FROM userprofile;
insert into userprofile(name, age, department_id) values ('hadoop', 12, 2);
END //
delimiter;
执行
call p1()
传参
in 仅用于传入参数
out 传出参数,用于返回值
inout 即可传入又可传出
与函数区别
函数有返回值
函数不能有 insert update delete create 语句
存储过程可以调用函数,函数不能调用存储过程
delimiter \\
create procedure p1(
in i1 int,
in i2 int,
inout i3 int,
out r1 int
)
BEGIN
-- 声明局部变量
DECLARE temp1 int;
DECLARE temp2 int default 0;
set temp1 = 1;
set r1 = i1 + i2 + temp1 + temp2;
set i3 = i3 + 100;
end\\
delimiter ;
-- 执行存储过程
-- 设置变量,以@开头
set @t1 =4;
set @t2 = 0;
-- 查看结果集1
CALL p1 (1, 2 ,@t1, @t2);
-- 查看结果集2
SELECT @t1,@t2;
CREATE DEFINER=`root`@`%` PROCEDURE `getAppendixModulus`(IN `id` int, IN `ge` VARCHAR(255))
BEGIN
-- 声明变量 modu 字符串类型 默认值是 ''
DECLARE modu VARCHAR(255) default '';
IF n > 100 THEN
-- case 逻辑
CASE ge
WHEN '99%' THEN
SET modu = 2.3265/SQRT(n);
WHEN '95%' THEN
SET modu = 1.6449/SQRT(n);
WHEN '90%' THEN
SET modu = 1.2815/SQRT(n);
END CASE;
ELSEIF n >= 2 AND n <= 100 THEN
SELECT modulu into modu from appendix_modulus WHERE appendix_modulus.n=n and appendix_modulus.guarantee_rate = ge;
END IF;
-- 保留三位小数
SELECT ROUND(modu, 3);
END
字符串截取
SUBSTRING_INDEX(SUBSTRING_INDEX('1,2,3,4,5', ',', 2), ',', -1)
根据 , 分割,先取前两位 再取结果中的最后一位 也就是整体的第二位
userprofile 中有10万条数据,如果没有索引,查询的时候MySQL会从第一行开始遍历,直到找到符合条件的。
如果将数据通过一定的方法进行存储,查询的时候,不需要再从第一行开始,这就是索引。
MySQL中有两种
B-TREE
MyISAM和InnoDB默认,不能修改,多路搜索树,查询速度默认是log(n), 左节点小于根节点,右节点大于根节点,左右子树都是搜索树,红黑树 平衡树,高度比B树高,树的查找性能取决于树的高度,高度越高,性能越低,在内存种,红黑树比B树性能好,在文件系统种,B树比红黑树好
HASH
单值查询比较快,范围查询比较慢,HASH是无序的,用作order by group by ,比较慢精确查询单值比较快,范围慢(hash表是无序的),不能用于排序和分组,查询速度是O(1)
创建索引(会额外创建文件保存这种数据结构)
create index id_index on userprofile(id);
注意 如果是BLOB TEXT 类型,要指定他的长度
create index ix_extra on in1(extra(32))
删除
drop id_index on userprofile;
查看
show index from userprofile;
Non_unique 如果索引不能包括重复词,则为0。如果可以,则为1
Key_name 索引的名称
Seq_in_index 索引中的列序列号,从1开始
Column_name 列名称
Collation 列以什么方式存储在索引中。在MySQL中,有值A(升序)或NULL(无分类)
Cardinality 索引中唯一值的数目的估计值
Sub_part 如果列只是被部分地编入索引,则为被编入索引的字符的数目。如果整列被编入索引,则为NULL
Packed 指示关键字如何被压缩。如果没有被压缩,则为NULL。
Null 如果列含有NULL,则含有YES。如果没有,则该列含有NO。
Index_type 用过的索引方法(BTREE, FULLTEXT, HASH, RTREE)
B-TREE B树
FULLTEXT 全文索引
HASH HASH索引
分类
普通索引:加速查找
唯一索引: 加速,唯一约束(可以为NULL)
create unique index id_index on userprofile(id);
主键索引:加速, 唯一约束(不可为NULL,因为主键不能为NULL)
组合索引:将多个列设置为索引(同时用多条件查询,在该条件下,比单个索引效率高,遵循最左前缀)
create index id_email_index on userprofile(id, email)
最左前缀,匹配
select * from userprofile where id=9999;
select * from userprofile where id=9999 and email='apache333@tomcat';
不匹配
select * from userprofile where email='apache333@tomcat';
覆盖索引:查询的索引那列,可以直接从索引文件中获取,索引文件被读到内存中
create index id_index on userprofile(id);
select id from userprofile where id=999;
索引合并:多个单列索引,合并使用
select * from userprofile where id=999 and email='[email protected]';
不命中
like ‘%xx’
select * from userprofile where name link '%spart';
使用函数
select * from userprofile where reverse(email)='apache';
or
select * from userprofile where id=999 or name='spart';
-- 以下会走索引
select * from userprofile where id=99999 or email='apache333@tomcat'
select * from userprofile where id=9999 or name='spart' and email='apache3300@tomcat'
类型不一致
注意字符串和字符串数字 email=999 和 email=‘999’
!= 如果是主键会走索引
> 如果是主键或索引是整数,会走索引
order by 如果是索引列,或主键,会走索引
执行计划
explain 预估执行时间
all < index < range < index_merge < ref_or_null < ref < eq_ref < system/const
explain select * from userprofile; type: ALL (全表扫描)
explain select * from userprofile where id=9999; type:const (索引)
优化方案
一对一:一个学生只有一个档案,一个学号
一对多:一个学生属于一个班,一个班有多个学生,学生表里有班级表的外键
多对多:一个学生可以选择多门课,一门课有多个学生选,多出一个关系表
隔离级别
操作
事务默认提交
show variables like 'autocommit'; -- 查看是否默认提交
set autocommit=0; -- 关闭自动提交
查看事务隔离级别
SELECT @@global.tx_isolation; -- 查看全局
SELECT @@session.tx_isolation; -- 查看会话
SELECT @@tx_isolation; -- 查看当前会话
读未提交
A:
-- 修改当前会话级别
set session TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
start TRANSACTION; --开始事务
select * from `user`;
-- 等B更新了,未提交再查,读到已修改未提交的内容 ---> 如果B回滚,A就会产生脏读
select * from `user`;
B:
set session TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
start TRANSACTION;
update `user` set `name`='sparknb' where id=6;
读已提交
A:
-- 修改当前会话级别
set session TRANSACTION ISOLATION LEVEL READ COMMITTED;
start TRANSACTION; --开始事务
-- 等B更新了,未提交再查,读到未修改未提交的内容,当B提交后,A读到修改后的内容 ---> 重复读的时候数据可能不一致, 不可重复读
select * from `user`;
B:
set session TRANSACTION ISOLATION LEVEL READ COMMITTED;
start TRANSACTION;
update `user` set `name`='sparknb' where id=6;
COMMIT; -- 提交
可重复读
A:
set session TRANSACTION ISOLATION LEVEL REPEATABLE READ;
start TRANSACTION;
select * from `user`;
6 sparknp 1
-- 等B更新了,提交后,读取的还是原来内容,
select * from `user`;
-- 当A做更新的时候,会发现结果是已经修改过的,------> 此时出现幻读
update `user` set `name`=CONCAT(name, '456') where id=6;
select * from `user`;
6 spark123456 1
B:
set session TRANSACTION ISOLATION LEVEL REPEATABLE READ;
start TRANSACTION;
update `user` set `name`='spark123' where id=6;
COMMENT;
串行
A:
set session TRANSACTION ISOLATION LEVEL SERIALIZABLE;
start TRANSACTION;
select * from `user`;
-- 未提交之前 A会锁住数据库,不让其他事务执行
COMMIT;
B:
set session TRANSACTION ISOLATION LEVEL SERIALIZABLE;
start TRANSACTION;
-- A开启一个事务执行查询操作,未提交,此时B开启一个事务执行插入,会阻塞,等待A提交后,B才会继续执行插入操作
INSERT INTO `bbs`.`user` (`id`, `name`, `department_id`) VALUES ('11', 'kafka', '1');
MYSQL默认是可重复读级别
MYSQL 主从复制的时候,使用binlog来恢复数据,就是在master上执行的顺序为先删后插!而此时binlog为STATEMENT格式,它记录的顺序为先插后删!所以采用可重复读
排它锁 称为写锁,若事务T对对象A加上X锁,则只允许T读取和修改A,其他任何事物都不能再对A 加任何锁,直到T释放A上的锁。
共享锁 称为读锁,若事务T对数据对象A加上S锁,则事务T可以读A,但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。
活锁 事务T1封锁了R,T2又请求封锁R,于是T2等待,T3也请求封锁R,当T1释放了R 上的锁,系统首先批准了T3的请求,T2继续等待,这就是活锁。
死锁 事务T1封锁了R1,T2封锁了R2,T1又请求封锁R2,因为T2已经封锁了R2,于是T1等待T2释放R2上的锁,接着T2又申请封锁R1,因为T1已经封锁了R1,T2只能等待T1释放R1上的锁,这就是死锁。
解决死锁的方法:一次封锁法 每个事务必须将所有要使用的数据全部加锁,否则就不能执行,弊端 加大封锁范围,降低了并发速度。
*一张表里,id是自增主键,当insert 17条记录,删除了第15,16,17条记录,再重启mysql,再insert一条记录,此时ID是多少?
如果表引擎类型是MyISAM,是18,会把自增主键最大记录到文件里,重启不会丢失
如果是InnoDB,是15,会把自增主键记录到内存里,重启或对表进行OPTIMIZE操作,都会丢失。
1、查找最晚入职员工的所有信息
select * from employees order by hire_date desc limit 1
可能有多条数据
select * from employees where hire_date in (select max(hire_date) from employees)
2、查找入职员工时间排名倒数第三的员工
select * from employees where hire_date = (select hire_date from employees order by hire_date desc limit 2,1);
3、查找当前薪水详情以及部门编号dept_no
select s.*, d.dept_no from salaries s, dept_manager d where s.emp_no=d.emp_no and d.to_date='9999-01-01' and s.to_date='9999-01-01';
select salaries.*, dept_manager.dept_no from salaries join dept_manager on dept_manager.emp_no=salaries.emp_no and dept_manager.to_date='9999-01-01' and salaries.to_date='9999-01-01';
4、查找所有已经分配部门的员工的last_name和first_name
select employees.last_name, first_name, dept_emp.dept_no from employees inner join dept_emp on employees.emp_no=dept_emp.emp_no
5、查找所有员工的last_name和first_name以及对应部门编号dept_no,也包括展示没有分配具体部门的员工
select employees.last_name, first_name, dept_emp.dept_no from employees left join dept_emp on employees.emp_no=dept_emp.emp_no
6、查找薪水涨幅超过15次的员工号emp_no以及其对应的涨幅次数t(由于COUNT()函数不可用于WHERE语句中,故使用HAVING语句来限定t>15的条件)
select emp_no,count(emp_no) as t from salaries group by emp_no having t > 15;
7、找出所有员工当前(to_date=‘9999-01-01’)具体的薪水salary情况,对于相同的薪水只显示一次,并按照逆序显示
select distinct salary from salaries where to_date='9999-01-01' order by salary desc;