文章目录
- 数据库应用
- 配置环境变量
- Mysql数据库
- SQL语句
- 数据库常用操作
- 表的常用操作
- 表记录的常用操作
- 数据类型
- 准备数据
- 字段约束
- 基础函数
- 条件查询
- #聚合函数
- #分组
- 分组后的过滤
- 字段约束
- 事务 transaction
- 约束
- 表关联 association
- 多表联查 join
- SQL的执行顺序
- 索引 index
- 视图View
- SQL优化
- 创建mysql-db库
- 准备student表
- 准备tb_dept表
- 准备tb_user表
- 查询SQL尽量不要使用select *,而是具体字段
- 避免在where子句中使用or来连接条件
- 使用varchar代替char
- 尽量使用数值替代字符串类型
- 查询尽量避免返回大量数据
- 使用explain分析你SQL执行计划
- 是否使用了索引及其扫描类型
- 创建name字段的索引
- 优化like语句
- 字符串怪现象
- 索引不宜太多,一般5个以内
- 索引不适合建在有大量重复数据的字段上
- where限定查询的数据
- 避免在where中对字段进行表达式操作
- 去重distinct过滤字段要少
- where中使用默认值代替null
- 批量插入性能提升
- 批量删除优化
- 伪删除设计
- 提高group by语句的效率
- 复合索引最左特性
- 排序字段创建索引
- 删除冗余和重复的索引
- 不要有超过5个以上的表连接
- inner join 、left join、right join,优先使用inner join
- in子查询的优化
- 数据库设计的三范式
本人发布的一系列资料都是学习备份使用,以及方便日后复习,随着技术的不断提升,每个文章都会持续添加内容,或者是扩展文章,本是秉承着分享的心态,互相学习,互相提升,也希望提出更好的意见,以及学习思路,欢迎留言,欢迎评论,谢谢!!
数据库应用
概念
什么是数据库
简而言之,就是存储数据,管理数据的仓库。
常见的数据库分为:
- 关系型数据库, Oracle、MySQL、SQLServer、Access
- 非关系型数据库, MongoDB、Redis、Solr、ElasticSearch、Hive、HBase
关系型和非关系型
早期发展的数据库建立在数据的紧密关系基础之上(如:父子关系、师生关系),我们称其为关系型数据库,也称为传统数据库;现今数据库建立在数据的松散关系基础之上(如:中国人和美国人、中国人和印度人、视频、音频),我们称其为非关系型数据库nosql(not only sql)。业界总在争论nosql能否干掉传统数据库,很多初学者也有这个困惑。以我来看,两者没有矛盾,它们各有特点,根据业务情况互补才是真谛。但总的来说原来关系型数据库一统天下的格局早被打破,领土不断被蚕食,规模一再的缩小,虽然无法全面被替代,但却早已风光不在,沦落到一偶之地,Oracle的衰落就是最好的证明,早期只要是全球大企业无一例外都是部署Oracle,但现在都在去Oracle化,阿里就已经全面排斥Oracle。
既然干不掉,很多传统项目的还是围绕关系型数据库的居多,所以我们先来学习关系型数据库,目前最流行的关系型数据库是MySQL。
关系型数据库
关系型数据库有特定的组织方式,其以行和列的形式存储数据,以便于用户理解。关系型数据库这一系列的行和列被称为表,一组表组成了数据库。用户通过查询来检索数据库中的数据,而查询是一个用于限定数据库中某些区域的执行代码。关系模型可以简单理解为二维表格模型,而一个关系型数据库就是由二维表及其之间的关系组成的一个数据集合。
配置环境变量
这里就不安装了,请大家自行安装数据库,话不多说直接上干货
注意 我这里安装的是MariaDB 不论是安装mysql还是MariaDB都是需要配置环境变量的,
mysql -V 查看版本,测试有没有安装成功
Mysql数据库
- mysql服务端,它来处理具体数据维护,保存磁盘
- mysql客户端,CRUD新增,修改,删除,查询
MySQL数据存放在哪里?
在MySQL的配置文件my.ini中会进行默认配置
MySQL客户端1:DOS窗口
mysql -uroot -proot
语法:mysql.exe执行文件
代表参数
-u 用户名,紧接着写的
-p 密码,紧接着写的
SQL语句
定义
结构化查询语言(Structured Query Language)简称SQL(发音:/ˈes kjuː ˈel/ “S-Q-L”),是一种特殊目的的编程语言,是一种数据库查询和程序设计语言,用于存取数据以及查询、更新和管理关系数据库系统;同时也是数据库脚本文件的扩展名。
SQL 是1986年10 月由美国国家标准局(ANSI)通过的数据库语言美国标准,接着,国际标准化组织(ISO)颁布了SQL正式国际标准。
分类
-
DML(Data Manipulation Language)数据操纵语言
如:insert,delete,update,select(插入、删除、修改、检索)简称CRUD操新增Create、查询Retrieve、修改Update、删除Delete -
DDL(Data Definition Language)数据库定义语言
如:create table之类 -
DCL(Data Control Language)数据库控制语言
如:grant、deny、revoke等,只有管理员才有相应的权限 -
DQL(Data Query Language)数据库查询语言
如: select 语法
注意:SQL不区分大小写
数据库常用操作
如果在dos窗口下中文数据乱码,执行下面命令:
1.set names gbk;
建库
- 创建数据库,数据库名称:message
两种写法:
写法1:create database message DEFAULT CHARACTER SET utf8;
写法2:create database message charset utf8;
删库
- 删除名称是message的数据库
drop database message;
查看所有数据库
- 查看所有数据库
show databases;
表的常用操作
使用数据库:use message;
表设计
门店表: tb_door
订单详情表: tb_order_detail
创建表
- 创建tb_door表,有id,door_name,tel字段
create table tb_door(
id int primary key auto_increment,
door_name varchar(100),
tel varchar(50)
);
修改表
- 添加列
alter table tb_door add column money NUMERIC(7,2)
删除表
- 删除名称是tb_door的表
drop table tb_door;
查看所有表
- 查看所有表
show tables;
查看表结构/设计表
- 查看tb_door表结构
desc tb_door;
表记录的常用操作
插入记录
- 向tb_door表中插入2条记录
insert into tb_door values(null,'永和大王1店',666);
insert into tb_door values(null,' 永和大王2店',888);
查询记录
- 查询tb_door表中的所有记录
SELECT * FROM tb_door;
修改记录
- 修改tb_door表中id为1的记录
update tb_door set tel=555 where id=1;
删除记录
- 删除tb_door表中id为2的数据
Delete from tb_door where id=2;
排序
- 将tb_door表记录按照tel排序
Select * from tb_door order by tel desc;
记录总数
- 查询tb_door表中的总记录数
Select count(*) from tb_door;
数据类型
命名规则
- 字段名必须以字母开头,尽量不要使用拼音
- 长度不能超过30个字符(不同数据库,不同版本会有不同)
- 不能使用SQL的保留字,如where,order,group
- 只能使用如下字符az、AZ、0~9、$ 等
- Oracle习惯全大写:USER_NAME,mysql习惯全小写:user_name
- 多个单词用下划线隔开,而非java语言的驼峰规则
字符
- char长度固定,不足使用空格填充,最多容纳2000个字符,char(11)存储abc,占11位。查询速度极快但浪费空间
- varchar变长字符串,最多容纳4000个字符,varchar(11)存储abc,只占3位。查询稍慢,但节省空间。Oracle为varchar2
- 大文本: 大量文字(不推荐使用,尽量使用varchar替代)
以utf8编码计算的话,一个汉字在u8下占3个字节
注:不同数据库版本长度限制可能会有不同
数字
- tinyint,int整数类型
- float,double小数类型
- numeric(5,2) decimal(5,2)—也可以表示小数,表示总共5位,其中可以有两位小数
- decimal和numeric表示精确的整数数字
日期
- date 包含年月日
- time时分秒
- datetime包含年月日和时分秒
- timestamp时间戳,不是日期,而是从1970年1月1日到指定日期的毫秒数
图片
- blob 二进制数据,可以存放图片、声音,容量4g。早期有这样的设计。但其缺点非常明显,数据库庞大,备份缓慢,这些内容去备份多份价值不大。同时数据库迁移时过大,迁移时间过久。所以目前主流都不会直接存储这样的数据,而只存储其访问路径,文件则存放在磁盘上。
注意一般我们不会把图片,音频以及视频放到数据库里面,一般会放到电脑磁盘中,然后把相应的路径放到数据库中,这样我们只需要从数据库中获取到相应的路径,然后使用流进行读写即可。
准备数据
准备数据方便我们下面的测试练习
部门表 dept
CREATE TABLE dept(
deptno int primary key auto_increment ,
dname VARCHAR(20),
loc VARCHAR(13)
);
INSERT INTO dept VALUES(null,'accounting','一区');
INSERT INTO dept VALUES(null,'research','二区');
INSERT INTO dept VALUES(null,'operations','二区');
员工表 emp
Mysql:
CREATE TABLE emp(
empno int primary key auto_increment,
ename VARCHAR(10),
job VARCHAR(10),
mgr int,
hiredate DATE,
sal double,
comm NUMERIC(7,2),
deptno int
);
INSERT INTO emp VALUES(100,'jack','副总',NULL,'2002-05-1',90000,NULL,1);
INSERT INTO emp VALUES(200,'tony','总监',100,'2015-02-02',10000,2000,2);
INSERT INTO emp VALUES(300,'hana','经理',200,'2017-02-02',8000,1000,2);
INSERT INTO emp VALUES(400,'leo','员工',300,'2019-02-22',3000,200.12,2);
INSERT INTO emp VALUES(500,'liu','员工',300,'2019-03-19',3500,200.58,2);
用户表 user
Mysql:
CREATE TABLE USER(
id INT ,
NAME VARCHAR(10) ,
pwd INT , age INT);
INSERT INTO USER VALUES(1,laowang,123,20);
INSERT INTO USER VALUES(2,zhangsan,123,18);
INSERT INTO USER VALUES(3,lisi,123456,25);
INSERT INTO USER VALUES(4,wangwu,456,28);
INSERT INTO USER VALUES(5,zhaoliu,789,30);
字段约束
主键约束
主键约束:如果为一个列添加了主键约束,那么这个列就是主键,主键的特点是唯一且不能为空。通常情况下,每张表都会有主键。
添加主键约束,例如将id设置为主键:
主键自增策略:当主键为数值类型时,为了方便维护,可以设置主键自增策略(auto_increment),设置了主键自增策略后,数据库会在表中保存一个AUTO_INCREMENT变量值,初始值为1,当需要id值,不需要我们指定值,由数据库负责从AUTO_INCREMENT获取一个id值,作为主键值插入到表中。而且每次用完AUTO_INCREMENT值,都会自增1. AUTO_INCREMENT=1
create table abc(
id int primary key auto_increment
);
insert into abc values(null);
insert into abc values(null);
insert into abc values(null);
select * from abc;
非空约束
非空约束:如果为一个列添加了非空约束,那么这个列的值就不能为空,但可以重复。
添加非空约束,例如为password添加非空约束:
create table user(
id int primary key auto_increment,
password varchar(50) not null
);
show tables;
insert into user values(null,null);//不符合非空约束
insert into user values(null,123;);//OK
唯一约束
唯一约束:如果为一个列添加了唯一约束,那么这个列的值就必须是唯一的(即不能重复),但可以为空。
添加唯一约束,例如为username添加唯一约束及非空约束:
create table test(
id int primary key auto_increment,
username varchar(50) unique--唯一约束
);
show tables;
insert into test values(null,'lisi');
insert into test values(null,'lisi');--username的值要唯一,重复会报错的
select * from test;
基础函数
#查询语句的多种写法
#查询 所有列
SELECT * FROM emp;
#查询 name列,高效
SELECT ename FROM emp;
#查询 name列 别名
SELECT ename abc FROM emp;#给字段设置别名
# 字段名 别名 字段名 别名
SELECT ename AS A ,job AS B FROM emp;
SELECT ename A ,job B FROM emp;
#upper(s) 转大写,将字符串 s 的所有字母变成大写字母
SELECT ename,UPPER(ename) FROM emp;
#lower(s) 转小写,将字符串 s 的所有字母变成小写字母
SELECT ename,LOWER(ename) FROM emp;
#length(s)求长度
SELECT ename,LENGTH(ename) FROM emp;
#一个字母或数字长度为1,一个汉字长度为3
SELECT job,LENGTH(job) FROM emp;
#substr(s, start, length) 截取,从字符串 s 的 start 位置截取长度为 length 的子字符串
SELECT ename,SUBSTR(ename,1,2) FROM emp;
#concat((s1,s2...sn)拼接,字符串 s1,s2 等多个字符串合并为一个字符串
SELECT ename,CONCAT(ename,job,'hello',123) AS a FROM emp;
#repalce(s,s1,s2) 替换,将字符串 s2 替代字符串 s 中的字符串 s1
SELECT ename , REPLACE(ename,'a' ,'b') FROM emp;
#ifnull(v1,v2) 如果是null就替换,v1 的值不为 NULL,则返回 v1,否则返回 v2。
SELECT comm ,IFNULL(comm,100) FROM emp;
小数
#round(a)把 a 的值取整,四舍五入
SELECT comm , ROUND(comm) FROM emp;
#round(a,b)把a的值保留b位小数,四舍五入
SELECT comm , ROUND(comm,1) FROM emp;
#ceil(a)向上取a的整数 & floor(a)向下取a的整数
SELECT comm , CEIL(comm) , FLOOR(comm) FROM emp;
日期处理
#now() 返回当前日期和时间
年 月 日 时 分 秒
#& year & month & day & hour & minute & second
SELECT NOW() ,
YEAR(NOW()) , MONTH(NOW()) , DAY(NOW()) ,
HOUR(NOW()), MINUTE(NOW()) , SECOND(NOW())
转义字符
#转义字符 '
SELECT 'xi\'an';
SELECT "xi'an";
条件查询
#distinct 去重,注意位置
#执行顺序 from dept select distinct loc
SELECT DISTINCT loc FROM dept ;
#where:条件满足才查
#语法:select 字段名 from 表名 where 字段名=字段值
#练习1:查询地址在一区的部门
SELECT deptno FROM dept WHERE loc='一区';
#练习2:查询2号部门的所有数据
SELECT * FROM dept WHERE deptno = 2;
#练习3:查询办公地址在二区的部门名称
SELECT dname FROM dept WHERE loc='二区';
#练习4:查询编号<3的部门地址
SELECT loc FROM dept WHERE deptno<3;
#练习5:查询地址在二区的,编号是3的部门名称
#and / or 如果查询条件有多个,使用 and并且 or或者 连接
SELECT dname FROM dept WHERE loc = '二区' AND deptno = 3;
#练习6:查询编号是1或者是2的部门名称
SELECT dname FROM dept WHERE deptno=1 OR deptno = 2;
SELECT dname FROM dept WHERE deptno IN(1,2);#效果同上
#练习7:查询地址在一区,或者编号是3的部门名称
SELECT dname FROM dept WHERE loc='一区' OR deptno=3;
#用user表,测试where也可以对增删改设置条件
#练习8:修改3号用户的密码
UPDATE USER SET pwd = 1123 WHERE id =3;
#练习9:修改密码是123,age=20的用户名
UPDATE USER SET NAME='ergou' WHERE pwd=123 AND age = 20;
#练习10:删除5号用户
DELETE FROM USER WHERE id=5;
#like模糊查询,%(百分号)是通配符(0~n个字符),_(下划线)是通配符(1个字符)
#练习1:查询名字里以t开头的员工的姓名和岗位
SELECT ename,job FROM emp WHERE ename LIKE 't%';#以t开头,高效
#练习2:查询名字包含o的员工姓名和编号
SELECT ename,empno FROM emp WHERE ename LIKE '%o%';#包含o
SELECT ename,empno FROM emp WHERE ename LIKE '%o';#以o结束
#null,对null特殊处理
#练习1:查询没有奖金的员工姓名和岗位
SELECT ename,job FROM emp WHERE comm IS NULL;
#练习2:查询有奖金的员工姓名和岗位
SELECT ename,job FROM emp WHERE comm IS NOT NULL;
#between and 区间范围 两遍都包含
#练习:查询工资在5000~10000的员工姓名和入职日期
SELECT ename,hiredate FROM emp WHERE sal>= 5000 AND sal<=10000;
SELECT ename,hiredate FROM emp WHERE sal BETWEEN 5000 AND 10000;
#练习:查询在19年入职的员工姓名
SELECT ename FROM emp WHERE hiredate BETWEEN '2019-1-1' AND '2019-12-31';
SELECT ename FROM emp WHERE hiredate>= '2019-1-1' AND hiredate<='2019-12-31';
SELECT ename FROM emp WHERE YEAR(hiredate)=2019;
#limit 分页
SELECT * FROM emp LIMIT 2; # 从第一条开始取,取2条
SELECT * FROM emp LIMIT 0,2; # 从第0+1条开始取,取2条
SELECT * FROM emp LIMIT 1,3; # 从第1+1条开始取,去3条
# order by 字典顺序排序,默认升序ASC,降序DESC
#练习:按照工资排序
SELECT * FROM emp ORDER BY sal DESC ;
#练习:按照名字排序
SELECT * FROM emp ORDER BY ename;
#练习:按照日期排序
SELECT * FROM emp ORDER BY hiredate;
#练习:按照岗位排序
SELECT * FROM emp ORDER BY job;
#练习:查询最高薪的员工的名字和岗位
SELECT ename,job FROM emp ORDER BY sal DESC LIMIT 0,1 ;
#练习:模拟用户登录的过程
#就是拿着用户名和密码去查库,查到了就登录查不到就不行
SELECT * FROM USER WHERE NAME = 'ergou'AND pwd='123';
#练习:模拟用户注册的过程
#就是拿着用户名和密码去 入库
INSERT INTO USER VALUES(5,'laowang',666666,24);
#聚合函数
#聚合函数 max min sum avg count
#练习:统计部门每个月的工资支出
SELECT sal,comm,sal+IFNULL(comm,0) FROM emp;
SELECT sal,comm,(sal+IFNULL(comm,0))*12 FROM emp;
#练习:统计2019年以前入职的员工信息
SELECT * FROM emp WHERE YEAR(hiredate)<2019;
SELECT * FROM emp WHERE hiredate <'2019-1-1';
#练习:求最高薪
SELECT MAX(sal) FROM emp ;
#练习:求最低薪
SELECT MIN(sal) FROM emp ;
#练习:求工资的总和
SELECT SUM(sal) FROM emp;
#练习:求平均工资
SELECT AVG(sal) FROM emp;
#练习:求个数
SELECT COUNT(comm) FROM emp;#null 不统计,导致计算不正确,低效
SELECT COUNT(sal) FROM emp;# 不推荐
SELECT COUNT(*) FROM emp;
SELECT COUNT(1) FROM emp;#高效
#练习:统计2019年以前入职的员工人数
SELECT COUNT(1) FROM emp WHERE YEAR(hiredate)<'2019';
#练习:统计2019年以前入职的员工的平均工资
SELECT AVG(sal) FROM emp WHERE YEAR(hiredate)<'2019';
#练习:统计2号部门的最高薪
SELECT MAX(sal) FROM emp WHERE deptno = 2;
#练习:统计岗位是员工的平均工资
SELECT AVG(sal) FROM emp WHERE job='员工';
#分组
#分组 group
#问题1:什么时候必须要分组?出现了混合列的时候
#问题2:
#当查询结果中,出现了混合列的时候,必须分组!!!
#聚合列,使用了聚合函数,非聚合列,没有使用聚合函数
SELECT job,AVG(sal) FROM emp
#按照合理的需求分组:job deptno hiredate
#练习:统计每个岗位的平均工资
SELECT job,AVG(sal) FROM emp GROUP BY job ORDER BY SAL DESC;
#练习:统计每个岗位的员工人数并排序
SELECT job,COUNT(1) a
FROM emp
GROUP BY job
ORDER BY a DESC;
#练习:统计每个部门的最高薪
SELECT deptno,MAX(sal) FROM emp GROUP BY deptno;
#练习:统计每个部门的总人数
SELECT COUNT(1) FROM emp GROUP BY deptno;
#练习:统计每年入职的总人数
SELECT COUNT(1),YEAR(hiredate) FROM emp GROUP BY YEAR(hiredate);
#练习:统计每年入职的平均工资和
SELECT SUM(sal),YEAR(hiredate) AS Y FROM emp GROUP BY Y;
分组后的过滤
#having
#练习:统计每年入职的工资总支出,只要15年以后的数据
SELECT SUM(sal)*12 , YEAR(hiredate) AS Y
FROM emp
GROUP BY Y
HAVING Y> 2015;
#练习:统计每个部门的总人数,只要人数>2的数据
SELECT deptno,COUNT(1) AS a FROM emp
GROUP BY deptno
HAVING a>2;
字段约束
默认约束
#默认约束:使用default设置默认值
DROP TABLE x1;#删除表
CREATE TABLE x1(#创建表
id INT PRIMARY KEY AUTO_INCREMENT, #主键约束
sex CHAR(3) DEFAULT '男' #设置了默认值
);
#插入数据时,仍然需要指定具体值(用的少)
INSERT INTO x1 VALUES(NULL,'女') ;
INSERT INTO x1 VALUES(NULL,NULL) ;#如果存放的值为null,实际的值就是null
检查约束
#检查约束:CHECK,检查字段值是否合法
CREATE TABLE x2(
id INT PRIMARY KEY AUTO_INCREMENT,
age INT,
CHECK(age<100)
);
insert into x2 values(null,18,101);#这里101超出了100就会出现错误,一般不常用,对版本有要求
外键约束
#外键约束:::::
#约束1:子表中的主键的值必须取自主表
#约束1:主表的记录要想删除,必须保证子表没用过
CREATE TABLE tb_user(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(10),
age INT
);
CREATE TABLE tb_user_address(
user_id INT PRIMARY KEY,#主键
address VARCHAR(100),
#创建外键:
#语法:foreign key(当前表的主键名) references 对方表名(对方表主键)
FOREIGN KEY(user_id) REFERENCES tb_user(id)
);
事务 transaction
什么是事务
数据库事务(Database Transaction),是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。
简单的说:事务就是将一堆的SQL语句(通常是增删改操作)绑定在一起执行,要么都执行成功,要么都执行失败,即都执行成功才算成功,否则就会恢复到这堆SQL执行之前的状态。
下面以银行转账为例,A转100块到B的账户,这至少需要两条SQL语句:
给A的账户减去100元;
update 账户表 set money=money**-100** where name=‘A’;
给B的账户加上100元。
update 账户表 set money=money**+100** where name=‘B’;
如果在第一条SQL语句执行成功后,在执行第二条SQL语句之前,程序被中断了(可能是抛出了某个异常,也可能是其他什么原因),那么B的账户没有加上100元,而A却减去了100元,在现实生活中这肯定是不允许的。
如果在转账过程中加入事务,则整个转账过程中执行的所有SQL语句会在一个事务中,而事务中的所有操作,要么全都成功,要么全都失败,不可能存在成功一半的情况。
也就是说给A的账户减去100元如果成功了,那么给B的账户加上100元的操作也必须是成功的;否则,给A减去100元以及给B加上100元都是失败的。
用一句话来总结一下:用来保证 多条SQL 要么全成功要么全失败
事务4个特性ACID
一般来说,事务是必须满足4个条件(ACID):原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。
- 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中如果发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
- 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
- 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
事务并发(读问题)
事物的并发问题如何发生?
多个事务同时操作同一个数据库的相同数据时
事务的并发问题都有哪些?
- 脏读:一个事务读到了另一个事务还未提交的update数据,导致多次 查询的结果不一样
- 不可重复读:一个事务读到了另一个事务已经提交的update数据,导致多次查询结果不一致
- 幻读:一个事务读到了另一个事务已经提交的insert数据,导致多次查询的结果不一样
事物的并发问题如何解决?
通过设置隔离级别来解决并发问题
隔离级别
隔离级别 | 隔离描述 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
READ UNCOMMITTED | 读未提交 | × | × | × |
READ COMMITTED | 读已提交 | √ | × | × |
REPEATABLE READ | 可重复读 | √ | √ | × |
SERIALIZABLE | 串行化 | √ | √ | √ |
mysql 中默认第三个隔离级别 REPEATABLE READ
oracle中默认第二个隔离级别 READ COMMITTED
丢失更新(写问题)
定义
在事务的隔离级别内容中,能够了解到两个不同的事务在并发的时候可能会发生数据的影响。细心的话可以发现事务隔离级别章节中,脏读、不可重复读、幻读三个问题都是由事务A对数据进行修改、增加,事务B总是在做读操作。如果两事务都在对数据进行修改则会导致另外的问题:丢失更新。
解决
- 悲观锁:认为两个事务更新操作一定会发生丢失更新
1.解决:通过在语句后边添加for update来实现行级上锁,所以又称为“行级锁”,例如:select * from t_account t wheret.id=‘1’ for update; - 乐观锁:认为事务不一定会产生丢失更新,让事务进行并发修改,不对事务进行锁定
- 解决:由程序员自己解决,可以通过给数据表添加自增的version字段或时间戳timestamp,进行数据修改时,数据库会检测version字段或者时间戳是否与原来的一致,若不一致,抛出异常或者重新查询
注意
对于账户交易建议直接使用悲观锁,数据库的性能很高,并发度不是很高的场景两者性能没有太大差别。如果是交易减库存的操作可以考虑乐观锁,保证并发度。
查询mysql的隔离级别
在默认情况下,MySQL每执行一条SQL语句,都是一个单独的事务。如果需要在一个事务中包含多条SQL语句,那么需要手动开启事务和结束事务。
- 开启事务:start transaction;
- 结束事务:commit(提交事务)或rollback(回滚事务)。
在执行SQL语句之前,先执行strat transaction,这就开启了一个事务(事务的起点),然后可以去执行多条SQL语句,最后要结束事务,commit表示提交,即事务中的多条SQL语句所做出的影响会持久化到数据库中。或者rollback,表示回滚,即回滚到事务的起点,之前做的所有操作都被撤消了!
SELECT @@tx_isolation;
Repeatable Read(可重读)
MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。
事务处理
- 在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务
- 事务处理可以用来维护数据的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行
- 事务用来管理 insert、update、delete 语句,因为这些操作才会“破坏”数据,查询select语句是不会的
- MySQL默认数据库的事务是开启的,执行SQL后自动提交。
- MySQL的事务也可以改成手动提交,那就有两个步骤:先开启,写完SQL后,再手动提交。
实战
其实事务没有大家想象的那么难,大家不要心里有压力,话不多说,实战!
MySQL已经为我们提供了事务管理,默认是一条SQL一个事务,如果想要自己管理事务必须有下面的步骤:
1,开启事务:start transaction;
2,执行SQL:增删改的SQL
3,结束事务:commit提交 / rollback回滚
多条语句时,批量执行,事务提交
有了事务,多步操作就形成了原子性操作,高并发下也不会引起数据错乱
mysql的事务默认就是开启的 – 多条语句一起操作时,要么一起成功要么一起失败
第一个窗口:
mysql> use database1;
mysql> show tables;
mysql> start transaction; #开启事务
mysql> insert into dept values(null,'java','beijing'); #执行了增删改的SQL
mysql> commit; #提交事务,否则,别人查不到
mysql> ROLLBACK;#事务回滚,就不会再提交了
第二个窗口:
mysql> use cgb211001;
mysql> select * from dept; #如果1号窗口提交了事务就能查到新数据,否则查不到
约束
非空约束 not null
DROP TABLE IF EXISTS tb_user; #如果表存在则删除,慎用会丢失数据
CREATE TABLE tb_user(
id INT AUTO_INCREMENT,
NAME VARCHAR(30) UNIQUE NOT NULL,
age INT,
phone VARCHAR(20) UNIQUE NOT NULL,
email VARCHAR(30) UNIQUE NOT NULL,
PRIMARY KEY (id)
);
DESC tb_user;
#id为自增主键,null值无效,数据库会自动用下一个id值替代
#age因为运行为null,所以可以设置为null
INSERT INTO tb_user (id,age) VALUES(NULL,NULL);
唯一约束 unique
Name字段创建了唯一约束,插入数据时数据库会进行检查,如果插入的值相同,就会检查报错:
DROP TABLE IF EXISTS tb_user; #如果表存在则删除,慎用会丢失数据
CREATE TABLE tb_user(
id INT,
NAME VARCHAR(30) UNIQUE NOT NULL,
phone VARCHAR(20) UNIQUE NOT NULL,
email VARCHAR(30) UNIQUE NOT NULL,
PRIMARY KEY (id)
);
DESC tb_user;
INSERT INTO tb_user (id,NAME) VALUES(1,'tony');
INSERT INTO tb_user (id,NAME) VALUES(2,'tony');
执行上面语句出错:
Query : INSERT INTO tb_user (id,NAME) VALUES(2,'tony')
Error Code : 1062
Duplicate entry 'tony' for key 'name'
展示表结构:
DESC tb_user;
主键约束 primary key
主键是一条记录的唯一标识,具有唯一性,不能重复
DROP TABLE IF EXISTS tb_user; #如果表存在则删除,慎用会丢失数据
CREATE TABLE tb_user(
id INT,
NAME VARCHAR(30),
PRIMARY KEY (id)
);
INSERT INTO tb_user (id,NAME) VALUES(1,'tony');
INSERT INTO tb_user (id,NAME) VALUES(1,'hellen');
第二句插入就会报错:
Query : INSERT INTO tb_user (id,NAME) VALUES(1,'hellen')
Error Code : 1062
Duplicate entry '1' for key 'PRIMARY'
提示主键1的值已经存在,重复了
外键约束 foreign key
DROP TABLE IF EXISTS tb_user_address; #如果表存在则删除,慎用会丢失数据
DROP TABLE IF EXISTS tb_user; #如果表存在则删除,慎用会丢失数据
CREATE TABLE tb_user (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT, #自增主键
NAME VARCHAR(50) NOT NULL UNIQUE, #非空,唯一索引
sex CHAR(2) DEFAULT '男', #默认值
phone CHAR(18),
age INT,
CHECK (age>0 AND age<=200),
);
CREATE TABLE tb_user_address (
user_id INT PRIMARY KEY NOT NULL,
address VARCHAR(200),
foreign key(user_id) REFERENCES tb_user(id)
);
DESC tb_user;
tb_user_address中user_id字段录入tb_user表不存在的主键值,将报错
默认约束 default
默认值
DROP TABLE IF EXISTS tb_user; #如果表存在则删除,慎用会丢失数据
CREATE TABLE tb_user (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT, #自增主键
NAME VARCHAR(50) NOT NULL UNIQUE, #非空,唯一索引
sex CHAR(2) DEFAULT '男', #默认值
phone CHAR(18),
age INT,
createdTime DATE DEFAULT NOW()
);
DESC tb_user;
检查约束 check
很少使用,了解即可,录入age超过200将报错
DROP TABLE IF EXISTS tb_user; #如果表存在则删除,慎用会丢失数据
CREATE TABLE tb_user (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT, #自增主键
NAME VARCHAR(50) NOT NULL UNIQUE, #非空,唯一索引
sex CHAR(2) DEFAULT '男', #默认值
phone CHAR(18),
age INT,
CHECK (age>0 AND age<=200),
createdTime DATE DEFAULT NOW()
);
DESC tb_user;
表关联 association
概念
表table代表了生活中一个主体,如部门表dept,员工表emp。表关联则代表了表之间的关系,如:部门和员工,商品和商品分类,老师和学生,教室和学生。
同时,也要知道,表并不都有关系,它们形成自己的小圈子。如商品和商品详情一圈,部门和员工一圈,出圈就可能没关系了,如商品和员工无关,商品和学生无关。
下面我们讨论表的关系分为四种:
- 一对一 one to one QQ和QQ邮箱,员工和员工编号
- 一对多 one to many 最常见,部门和员工,用户和订单
- 多对一 many to one 一对多反过来,员工和部门,订单和用户
- 多对多 many to many 老师和学生,老师和课程
创建表
表设计特点:
- 表都以s结束,标识复数
- 字段多以表的首字母作为开头,在多表联查时,方便标识出是哪个表的字段
/*==============================================================*/
/* DBMS name: MySQL 5.0 */
/* Created on: 2020 */
/*==============================================================*/
drop table if exists courses;
drop table if exists scores;
drop table if exists students;
drop table if exists teachers;
/*==============================================================*/
/* Table: courses */
/*==============================================================*/
create table courses
(
cno varchar(5) not null,
cname varchar(10) not null,
tno varchar(3) not null,
primary key (cno)
);
/*==============================================================*/
/* Table: scores */
/*==============================================================*/
create table scores
(
sno varchar(3) not null,
cno varchar(5) not null,
degree numeric(10,1) not null,
primary key (sno, cno)
);
/*==============================================================*/
/* Table: students */
/*==============================================================*/
create table students
(
sno varchar(3) not null,
sname varchar(4) not null,
ssex varchar(2) not null,
sbirthday datetime,
class varchar(5),
primary key (sno)
);
/*==============================================================*/
/* Table: teachers */
/*==============================================================*/
create table teachers
(
tno varchar(3) not null,
tname varchar(4),
tsex varchar(2),
tbirthday datetime,
prof varchar(6),
depart varchar(10),
primary key (tno)
);
插入测试数据
INSERT INTO STUDENTS (SNO,SNAME,SSEX,SBIRTHDAY,CLASS) VALUES (108 ,'曾华' ,'男' ,'1977-09-01',95033);
INSERT INTO STUDENTS (SNO,SNAME,SSEX,SBIRTHDAY,CLASS) VALUES (105 ,'匡明' ,'男' ,'1975-10-02',95031);
INSERT INTO STUDENTS (SNO,SNAME,SSEX,SBIRTHDAY,CLASS) VALUES (107 ,'王丽' ,'女' ,'1976-01-23',95033);
INSERT INTO STUDENTS (SNO,SNAME,SSEX,SBIRTHDAY,CLASS) VALUES (101 ,'李军' ,'男' ,'1976-02-20',95033);
INSERT INTO STUDENTS (SNO,SNAME,SSEX,SBIRTHDAY,CLASS) VALUES (109 ,'王芳' ,'女' ,'1975-02-10',95031);
INSERT INTO STUDENTS (SNO,SNAME,SSEX,SBIRTHDAY,CLASS) VALUES (103 ,'陆君' ,'男' ,'1974-06-03',95031);
INSERT INTO TEACHERS(TNO,TNAME,TSEX,TBIRTHDAY,PROF,DEPART) VALUES (804,'易天','男','1958-12-02','副教授','计算机系');
INSERT INTO TEACHERS(TNO,TNAME,TSEX,TBIRTHDAY,PROF,DEPART) VALUES (856,'王旭','男','1969-03-12','讲师','电子工程系');
INSERT INTO TEACHERS(TNO,TNAME,TSEX,TBIRTHDAY,PROF,DEPART) VALUES (825,'李萍','女','1972-05-05','助教','计算机系');
INSERT INTO TEACHERS(TNO,TNAME,TSEX,TBIRTHDAY,PROF,DEPART) VALUES (831,'陈冰','女','1977-08-14','助教','电子工程系');
INSERT INTO COURSES(CNO,CNAME,TNO)VALUES ('3-105' ,'计算机导论',825);
INSERT INTO COURSES(CNO,CNAME,TNO)VALUES ('3-245' ,'操作系统' ,804);
INSERT INTO COURSES(CNO,CNAME,TNO)VALUES ('6-166' ,'模拟电路' ,856);
INSERT INTO COURSES(CNO,CNAME,TNO)VALUES ('6-106' ,'概率论' ,831);
INSERT INTO COURSES(CNO,CNAME,TNO)VALUES ('9-888' ,'高等数学' ,831);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (103,'3-245',86);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (105,'3-245',75);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (109,'3-245',68);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (103,'3-105',92);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (105,'3-105',88);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (109,'3-105',76);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (101,'3-105',64);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (107,'3-105',91);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (108,'3-105',78);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (101,'6-166',85);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (107,'6-106',79);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (108,'6-166',81);
多表联查 join
多表联查方法1:笛卡尔积
#多表联查方法1:笛卡尔积
#练习:查询部门表和员工表的所有数据
SELECT * FROM dept,emp;
#添加查询条件,表明两张表的关系
SELECT * FROM dept,emp #逗号隔开表名
#表名.字段名 表名.字段名
WHERE dept.deptno = emp.deptno;
#练习:查询课程表和老师表的所有数据
#select * from courses,teachers
SELECT courses.*,teachers.* FROM courses,teachers
WHERE courses.tno=teachers.tno;
#练习:查询在一区办公的员工名字
SELECT emp.ename FROM emp,dept
WHERE emp.deptno = dept.deptno#描述两表的关系
AND dept.loc = '一区';#在一区办公
#练习:查询在research部门工作的员工的信息
SELECT * FROM emp e,dept d
WHERE e.deptno= d.deptno
AND d.dname="research";
SELECT emp.*,COUNT(1) FROM emp,dept
WHERE emp.deptno= dept.deptno#描述两个表的关系
AND dept.dname="research" #在research
GROUP BY emp.deptno;
多表联查方式2:连接查询
#多表联查方式2:连接查询
#多表之间用join连接,用on来描述两张表的关系,用where表示
#练习:查询在一区办公的员工名字
SELECT emp.ename FROM emp JOIN dept
ON emp.deptno = dept.deptno #描述两表的关系
WHERE dept.loc='一区'; #在一区办公的
#练习:查询课程表和老师表的所有数据
SELECT * FROM courses JOIN teachers
ON courses.tno = teachers.tno ;
#练习:查询在research部门工作的员工的信息
SELECT emp.* FROM emp JOIN dept
ON emp.deptno=dept.deptno
WHERE dept.dname='research';
#练习:查询易天老师能讲的课程名称
SELECT courses.cname FROM courses JOIN teachers
ON courses.tno = teachers.tno
WHERE teachers.tname='易天';
#练习:查询学员李军的总得分
SELECT SUM(s.degree)
FROM scores s JOIN students s1
ON s.sno = s1.sno
WHERE s1.sname = '李军';
三种连接查询的区别
#练习:三种连接查询的区别
#连接查询原则:小表驱动大表(执行顺序:左表->判断->右表)
#内连接:inner join取交集
#左外连接:left join取左表的所有和右表满足的
#右外连接:right join取右表的所有和左表满足的
#练习:查询部门表和员工表的所有数据
SELECT * FROM emp INNER JOIN dept
ON emp.deptno = dept.deptno;
SELECT * FROM emp LEFT JOIN dept
ON emp.deptno = dept.deptno;
SELECT * FROM emp RIGHT JOIN dept
#左连接,取到左表的所有和右表满足条件的不满足的用null填充
ON emp.deptno = dept.deptno;
inner join、left join、right join的区别?
- INNER JOIN两边都对应有记录的才展示,其他去掉
- LEFT JOIN左边表中的数据都出现,右边没有数据以NULL填充
- RIGHT JOIN右边表中的数据都出现,左边没有数据以NULL填充
子查询 subquery
概念
子查询是指嵌入在其他select语句中的select语句,也叫嵌套查询。子查询执行效率低慎用。记录少时效率影响不大、图方便直接使用,记录多时最好使用其它方式替代。
单行子查询 =
返回结果为一个
--列出tony所在部门的所有人员
select deptno from emp where ename='tony';
select * from emp where deptno = (select deptno from emp where ename='tony');
多行子查询 in
in子查询
select * from emp where job in ('经理','员工');
select * from emp where job in (select distinct job from emp);
SQL的执行顺序
(1) FROM [left_table] 选择表
(2) ON <join_condition> 链接条件
(3) <join_type> JOIN <right_table> 链接
(4) WHERE <where_condition> 条件过滤
(5) GROUP BY <group_by_list> 分组
(6) AGG_FUNC(column or expression),... 聚合
(7) HAVING <having_condition> 分组过滤
(8) SELECT (9) DISTINCT column,... 选择字段、去重
(9) ORDER BY <order_by_list> 排序
(10) LIMIT count OFFSET count; 分页
索引 index
定义
索引是一种排好序的快速查找的数据结构,它帮助数据库高效的进行数据的检索。在数据之外,数据库系统还维护着满足特定查找算法的数据结构(额外的存储空间),这些数据结构以某种方式指向数据,这样就可以在这些数据结构上实现高效的查找算法。这种数据结构就叫做索引。
一般来说索引本身也很大,不可能全部存储在内存中,因此往往以索引文件的形式存放在磁盘中。目前大多数索引都采用BTree树方式构建。
分类
- 单值索引:一个索引只包括一个列,一个表可以有多个列
- 唯一索引:索引列的值必须唯一,但允许有空值;主键会自动创建唯一索引
- 复合索引:一个索引同时包括多列
创建索引
查看索引,主键会自动创建索引
show index from dept;
创建普通索引
#查询索引,表里的主键数据库已经创建好了索引
SHOW INDEX FROM dept;
SHOW INDEX FROM emp;
#创建索引 --不是所有字段都适合加索引,可以给经常查的字段加索引
#语法:create index 索引名 on 表名(字段名)
CREATE INDEX dname_index ON dept(dname);
SHOW INDEX FROM dept;
#查看SQL的执行计划/性能,观察查询结果里的key的值,如果不是null就是索引生效了
EXPLAIN
SELECT * FROM dept WHERE dname='research';
#练习:创建 查看 使用索引
CREATE INDEX ename_index ON emp(ename);
SHOW INDEX FROM emp;
EXPLAIN
SELECT * FROM emp WHERE ename='liu';
创建唯一索引
#创建唯一索引
#语法:create unique index 索引名 on 表名(字段名)
CREATE UNIQUE INDEX loc_index ON dept(loc);#失败的,loc的值重复了
SHOW INDEX FROM dept;
CREATE UNIQUE INDEX dname_index1 ON dept(dname);
#使用:dname字段拥有两种索引:唯一索引和单值索引,优先使用了唯一索引
EXPLAIN SELECT * FROM dept WHERE dname='research';
创建复合索引
#创建符合索引
CREATE INDEX job_deptno_index ON emp(job,deptno);
SHOW INDEX FROM emp;
#使用复合索引:最左特效(必须包含着最左边的元素)
EXPLAIN SELECT * FROM emp WHERE job='员工' AND deptno=2;
EXPLAIN SELECT * FROM emp WHERE job='员工';
EXPLAIN SELECT * FROM emp WHERE deptno=2; #失效!!!
EXPLAIN SELECT * FROM emp WHERE deptno=2 AND job='员工';
EXPLAIN SELECT * FROM emp WHERE deptno=2 OR job='员工'; #失效!!!
删除索引
#删除索引
ALTER TABLE emp DROP INDEX job_deptno_index;
SHOW INDEX FROM emp;
索引扫描类型
type:
- ALL 全表扫描,没有优化,最慢的方式
- index 索引全扫描,其次慢的方式
- range 索引范围扫描,常用语<,<=,>=,between等操作
- ref 使用非唯一索引扫描或唯一索引前缀扫描,返回单条记录,常出现在关联查询中
- eq_ref 类似ref,区别在于使用的是唯一索引,使用主键的关联查询
- const/system 单条记录,系统会把匹配行中的其他列作为常数处理,如主键或唯一索引查询,system是const的特殊情况
- null MySQL不访问任何表或索引,直接返回结果
为何索引快?
为何索引快?
明显查询索引表比直接查询数据表要快的多,首先,索引表是排序了,可以类似二分查找,非常有效的提高了查询的速度。
其过程如下图,先到事先排序好的索引表中检索查询,找到其主键后,就直接定位到记录所在位置,然后直接返回这条数据。
- 排序,tree结构,类似二分查找
- 索引表小
小结
优点:
- 索引是数据库优化
- 表的主键会默认自动创建索引
- 每个字段都可以被索引
- 大量降低数据库的IO磁盘读写成本,极大提高了检索速度
- 索引事先对数据进行了排序,大大提高了查询效率
缺点:
- 索引本身也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也要占用空间
- 索引表中的内容,在业务表中都有,数据是重复的,空间是“浪费的”
- 虽然索引大大提高了查询的速度,但对数据的增、删、改的操作需要更新索引表信息,如果数据量非常巨大,更新效率就很慢,因为更新表时,MySQL不仅要保存数据,也要保存一下索引文件
- 随着业务的不断变化,之前建立的索引可能不能满足查询需求,需要消耗我们的时间去更新索引
视图View
概念
可视化的表,视图当做是一个特殊的表,是指,把sql执行的结果,直接缓存到了视图中。
下次还要发起相同的sql,直接查视图。现在用的少,了解即可.
使用: 1,创建视图 2,使用视图
测试
create view 视图名 as SQL语句;
select * from 视图名;
#视图:就是一个特殊的表,缓存上次的查询结果
#视图:优点是避免了次次写复杂的SQL,屏蔽了业务表的复杂应,被所有用户共享
# 缺点:数据都是重复的,SQL无法被优化
#1.创建视图
CREATE VIEW emp_view AS
SELECT * FROM emp WHERE ename LIKE '%a%' #模糊查询,名字里包含a的
#2.使用视图
SELECT * FROM emp_view
#删除视图
DROP VIEW emp_ename;
#查看视图
SHOW CREATE VIEW emp_ename_view;
SQL优化
创建mysql-db库
CREATE DATABASE /*!32312 IF NOT EXISTS*/`mysql-db` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `mysql-db`;
准备student表
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` varchar(4) NOT NULL,
`NAME` varchar(20) DEFAULT NULL,
`sex` char(2) DEFAULT NULL,
`birthday` date DEFAULT NULL,
`salary` decimal(7,2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `student`(`id`,`NAME`,`sex`,`birthday`,`salary`) values ('1','张慎政','男','2020-01-01','10000.00'),('2','刘沛霞','女','2020-01-02','10000.00'),('3','刘昱江','男','2020-01-03','10000.00'),('4','齐雷','男','2020-01-04','20000.00'),('5','王海涛','男','2020-01-05','20000.00'),('6','董长春','男','2020-01-06','10000.00'),('7','张久军','男','2020-01-07','20000.00'),('8','陈子枢','男','2020-10-11','3000.00');
准备tb_dept表
DROP TABLE IF EXISTS `tb_dept`;
CREATE TABLE `tb_dept` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`parent_id` int(11) DEFAULT NULL,
`sort` int(11) DEFAULT NULL,
`note` varchar(100) DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`updated` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
insert into `tb_dept`(`id`,`name`,`parent_id`,`sort`,`note`,`created`,`updated`) values (1,'集团',0,1,'集团总部','2018-10-02 09:15:14','2018-09-27 16:35:54'),(2,'财务部',1,2,'财务管理','2018-09-27 16:35:52','2018-09-27 16:34:15'),(3,'软件部',1,3,'开发软件、运维','2018-09-27 16:35:54','2018-09-27 16:34:51');
准备tb_user表
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`dept_id` int(11) DEFAULT NULL,
`username` varchar(50) DEFAULT NULL,
`password` varchar(100) DEFAULT NULL,
`salt` varchar(50) DEFAULT NULL,
`email` varchar(100) DEFAULT NULL,
`mobile` varchar(100) DEFAULT NULL,
`valid` tinyint(4) DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`updated` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
insert into `tb_user`(`id`,`dept_id`,`username`,`password`,`salt`,`email`,`mobile`,`valid`,`created`,`updated`) values (1,1,'陈集团','123456',NULL,'tony@sina.com','13572801415',1,'2018-09-30 09:32:18','2018-09-30 09:32:18'),(2,3,'牛软件','567890',NULL,'niu@sina.com','13208737172',0,'2018-10-02 09:23:19','2018-09-20 09:32:18');
查询SQL尽量不要使用select *,而是具体字段
反例:SELECT * FROM student
正例:SELECT id,NAME FROM student
理由:
字段多时,大表能达到100多个字段甚至达200多个字段
只取需要的字段,节省资源、减少网络开销
select * 进行查询时,很可能不会用到索引,就会造成全表扫描
避免在where子句中使用or来连接条件
反例:SELECT * FROM student WHERE id=1 OR salary=30000
正例:
# 分开两条sql写
SELECT * FROM student WHERE id=1
SELECT * FROM student WHERE salary=30000
理由:
使用or可能会使索引失效,从而全表扫描
对于or没有索引的salary这种情况,假设它走了id的索引,但是走到salary查询条件时,它还得全表扫描。也就是说整个过程需要三步:全表扫描+索引扫描+合并。如果它一开始就走全表扫描,直接一遍扫描就搞定。虽然mysql是有优化器的,处于效率与成本考虑,遇到or条件,索引还是可能失效的
使用varchar代替char
反例:`deptname` char(100) DEFAULT NULL COMMENT '部门名称'
正例:`deptname` varchar(100) DEFAULT NULL COMMENT '部门名称'
理由:
varchar变长字段按数据内容实际长度存储,存储空间小,可以节省存储空间
char按声明大小存储,不足补空格
其次对于查询来说,在一个相对较小的字段内搜索,效率更高
尽量使用数值替代字符串类型
主键(id):primary key优先使用数值类型int,tinyint
性别(sex):0-代表女,1-代表男;数据库没有布尔类型,mysql推荐使用tinyint
支付方式(payment):1-现金、2-微信、3-支付宝、4-信用卡、5-银行卡
服务状态(state):1-开启、2-暂停、3-停止
商品状态(state):1-上架、2-下架、3-删除
查询尽量避免返回大量数据
如果查询返回数据量很大,就会造成查询时间过长,网络传输时间过长。同时,大量数据返回也可能没有实际意义。如返回上千条甚至更多,用户也看不过来。
通常采用分页,一页习惯10/20/50/100条。
使用explain分析你SQL执行计划
SQL很灵活,一个需求可以很多实现,那哪个最优呢?SQL提供了explain关键字,它可以分析你的SQL执行计划,看它是否最佳。Explain主要看SQL是否使用了索引。
EXPLAIN
SELECT * FROM student WHERE id=1
是否使用了索引及其扫描类型
type:
- ALL 全表扫描,没有优化,最慢的方式
- index 索引全扫描
- range 索引范围扫描,常用语<,<=,>=,between等操作
- ref 使用非唯一索引扫描或唯一索引前缀扫描,返回单条记录,常出现在关联查询中
- eq_ref 类似ref,区别在于使用的是唯一索引,使用主键的关联查询
- const/system 单条记录,系统会把匹配行中的其他列作为常数处理,如主键或唯一索引查询
- null MySQL不访问任何表或索引,直接返回结果
key:
- 真正使用的索引方式
创建name字段的索引
ALTER TABLE student ADD INDEX index_name (NAME)
优化like语句
模糊查询,程序员最喜欢的就是使用like,但是like很可能让你的索引失效
反例:
EXPLAIN
SELECT id,NAME FROM student WHERE NAME LIKE '%1'
EXPLAIN
SELECT id,NAME FROM student WHERE NAME LIKE '%1%'
正例:
EXPLAIN
SELECT id,NAME FROM student WHERE NAME LIKE '1%'
字符串怪现象
反例:
#未使用索引
EXPLAIN
SELECT * FROM student WHERE NAME=123
正例:
#使用索引
EXPLAIN
SELECT * FROM student WHERE NAME='123'
理由:
- 为什么第一条语句未加单引号就不走索引了呢?这是因为不加单引号时,是字符串跟数字的比较,它们类型不匹配,MySQL会做隐式的类型转换,把它们转换为数值类型再做比较
索引不宜太多,一般5个以内
- 索引并不是越多越好,虽其提高了查询的效率,但却会降低插入和更新的效率
- 索引可以理解为一个就是一张表,其可以存储数据,其数据就要占空间
- 再者,索引表的一个特点,其数据是排序的,那排序要不要花时间呢?肯定要
- insert或update时有可能会重建索引,如果数据量巨大,重建将进行记录的重新排序,所以建索引需要慎重考虑,视具体情况来定
- 一个表的索引数最好不要超过5个,若太多需要考虑一些索引是否有存在的必要
索引不适合建在有大量重复数据的字段上
如性别字段。因为SQL优化器是根据表中数据量来进行查询优化的,如果索引列有大量重复数据,Mysql查询优化器推算发现不走索引的成本更低,很可能就放弃索引了。
where限定查询的数据
数据中假定就一个男的记录
反例:
SELECT id,NAME FROM student WHERE sex='男'
正例:
SELECT id,NAME FROM student WHERE id=1 AND sex='男'
理由:
- 需要什么数据,就去查什么数据,避免返回不必要的数据,节省开销
避免在where中对字段进行表达式操作
反例:
EXPLAIN
SELECT * FROM student WHERE id+1-1=+1
正例:
EXPLAIN
SELECT * FROM student WHERE id=+1-1+1
EXPLAIN
SELECT * FROM student WHERE id=1
理由:
- SQL解析时,如果字段相关的是表达式就进行全表扫描
- 避免在where子句中使用!=或<>操作符
应尽量避免在where子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描。记住实现业务优先,实在没办法,就只能使用,并不是不能使用。如果不能使用,SQL也就无需支持了。
反例:
EXPLAIN
SELECT * FROM student WHERE salary!=3000
EXPLAIN
SELECT * FROM student WHERE salary<>3000
理由:
- 使用!=和<>很可能会让索引失效
去重distinct过滤字段要少
索引失效
EXPLAIN
SELECT DISTINCT * FROM student
索引生效
EXPLAIN
SELECT DISTINCT id,NAME FROM student
EXPLAIN
SELECT DISTINCT NAME FROM student
理由:
- 带distinct的语句占用cpu时间高于不带distinct的语句。因为当查询很多字段时,如果使用distinct,数据库引擎就会对数据进行比较,过滤掉重复数据,然而这个比较、过滤的过程会占用系统资源,如cpu时间
where中使用默认值代替null
修改表,增加age字段,类型int,非空,默认值0
ALTER TABLE student ADD age INT NOT NULL DEFAULT 0;
批量插入性能提升
大量数据提交,上千,上万,批量性能非常快,mysql独有
多条提交:
INSERT INTO student (id,NAME) VALUES(4,'齐雷');
INSERT INTO student (id,NAME) VALUES(5,'刘昱江');
批量提交:
INSERT INTO student (id,NAME) VALUES(4,'齐雷'),(5,'刘昱江');
理由:
- 默认新增SQL有事务控制,导致每条都需要事务开启和事务提交;而批量处理是一次事务开启和提交。自然速度飞升
- 数据量小体现不出来
批量删除优化
避免同时修改或删除过多数据,因为会造成cpu利用率过高,会造成锁表操作,从而影响别人对数据库的访问。
反例:
一次删除10万或者100万+?
delete from student where id <100000;
采用单一循环操作,效率低,时间漫长
for(User user:list){
delete from student;
}
正例:
分批进行删除,如每次500
for(){
delete student where id<500;
}
delete student where id>=500 and id<1000;
理由:
- 一次性删除太多数据,可能造成锁表,会有lock wait timeout exceed的错误,所以建议分批操作
伪删除设计
商品状态(state):1-上架、2-下架、3-删除
理由:
- 这里的删除只是一个标识,并没有从数据库表中真正删除,可以作为历史记录备查
- 同时,一个大型系统中,表关系是非常复杂的,如电商系统中,商品作废了,但如果直接删除商品,其它商品详情,物流信息中可能都有其引用。
- 通过where state=1或者where state=2过滤掉数据,这样伪删除的数据用户就看不到了,从而不影响用户的使用
- 操作速度快,特别数据量很大情况下
提高group by语句的效率
可以在执行到该语句前,把不需要的记录过滤掉
反例:先分组,再过滤
select job,avg(salary) from employee
group by job
having job ='president' or job = 'managent';
正例:先过滤,后分组
select job,avg(salary) from employee
where job ='president' or job = 'managent'
group by job;
复合索引最左特性
创建复合索引,也就是多个字段
ALTER TABLE student ADD INDEX idx_name_salary (NAME,salary)
满足复合索引的左侧顺序,哪怕只是部分,复合索引生效
EXPLAIN
SELECT * FROM student WHERE NAME='陈子枢'
没有出现左边的字段,则不满足最左特性,索引失效
EXPLAIN
SELECT * FROM student WHERE salary=3000
复合索引全使用,按左侧顺序出现 name,salary,索引生效
EXPLAIN
SELECT * FROM student WHERE NAME='陈子枢' AND salary=3000
虽然违背了最左特性,但MYSQL执行SQL时会进行优化,底层进行颠倒优化
EXPLAIN
SELECT * FROM student WHERE salary=3000 AND NAME='陈子枢'
理由:
- 复合索引也称为联合索引
- 当我们创建一个联合索引的时候,如(k1,k2,k3),相当于创建了(k1)、(k1,k2)和(k1,k2,k3)三个索引,这就是最左匹配原则
- 联合索引不满足最左原则,索引一般会失效,但是这个还跟Mysql优化器有关的
排序字段创建索引
什么样的字段才需要创建索引呢?原则就是where和order by中常出现的字段就创建索引。
使用 *,包含了未索引的字段,导致索引失效
EXPLAIN
SELECT * FROM student ORDER BY NAME;
name字段有索引
EXPLAIN
SELECT id,NAME FROM student ORDER BY NAME
排序字段未创建索引,性能就慢
EXPLAIN
SELECT id,NAME FROM student ORDER BY sex
删除冗余和重复的索引
SHOW INDEX FROM student
创建索引index_name
ALTER TABLE student ADD INDEX index_name (NAME)
删除student表的index_name索引
DROP INDEX index_name ON student ;
修改表结果,删除student表的index_name索引
ALTER TABLE student DROP INDEX index_name ;
主键会自动创建索引,删除主键索引
ALTER TABLE student DROP PRIMARY KEY ;
不要有超过5个以上的表连接
- 关联的表个数越多,编译的时间和开销也就越大
- 每次关联内存中都生成一个临时表
- 应该把连接表拆开成较小的几个执行,可读性更高
- 如果一定需要连接很多表才能得到数据,那么意味着这是个糟糕的设计了
- 阿里规范中,建议多表联查三张表以下
inner join 、left join、right join,优先使用inner join
三种连接如果结果相同,优先使用inner join,如果使用left join左边表尽量小
-
inner join 内连接,只保留两张表中完全匹配的结果集
-
left join会返回左表所有的行,即使在右表中没有匹配的记录
-
right join会返回右表所有的行,即使在左表中没有匹配的记录
理由: -
如果inner join是等值连接,返回的行数比较少,所以性能相对会好一点
-
同理,使用了左连接,左边表数据结果尽量小,条件尽量放到左边处理,意味着返回的行数可能比较少。这是mysql优化原则,就是小表驱动大表,小的数据集驱动大的数据集,从而让性能更优
in子查询的优化
日常开发实现业务需求可以有两种方式实现:
- 一种使用数据库SQL脚本实现
- 一种使用程序实现
如需求:查询所有部门的所有员工:
#in子查询
SELECT * FROM tb_user WHERE dept_id IN (SELECT id FROM tb_dept);
#这样写等价于:
#先查询部门表
SELECT id FROM tb_dept
再由部门dept_id,查询tb_user的员工
SELECT * FROM tb_user u,tb_dept d WHERE u.dept_id = d.id
假设表A表示某企业的员工表,表B表示部门表,查询所有部门的所有员工,很容易有以下程序实现,可以抽象成这样的一个嵌套循环:
List<> resultSet;
for(int i=0;i<B.length;i++) {
for(int j=0;j<A.length;j++) {
if(A[i].id==B[j].id) {
resultSet.add(A[i]);
break;
}
}
}
上面的需求使用SQL就远不如程序实现,特别当数据量巨大时。
理由:
- 数据库最费劲的就是程序链接的释放。假设链接了两次,每次做上百万次的数据集查询,查完就结束,这样就只做了两次;相反建立了上百万次链接,申请链接释放反复重复,就会额外花费很多实际,这样系统就受不了了,慢,卡顿
数据库设计的三范式
概述
简言之就是,数据库设计对数据的存储性能,还有开发人员对数据的操作都有莫大的关系。所以建立科学的,规范的的数据库是需要满足一些规范的来优化数据数据存储方式。在关系型数据库中这些规范就可以称为范式,也是作为数据库 设计的一些规则.
关系型数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)。范式越高,冗余最低,一般到三范式,再往上,表越多,可能导致查询效率下降。所以有时为了提高运行效率,可以让数据冗余.
1NF的定义为:符合1NF的关系中的每个属性都不可再分
2NF在1NF的基础之上,消除了非主属性对于码的部分函数依赖,也就是说,表里的每个字段都要依赖于主键
第一步:找出数据表中所有的码。
第二步:根据第一步所得到的码,找出所有的主属性。
第三步:数据表中,除去所有的主属性,剩下的就都是非主属性了。
第四步:查看是否存在非主属性对码的部分函数依赖
3NF在2NF的基础之上,消除了非主属性对于码的传递函数依赖
就是指没个属性都跟主键有直接关系而不是间接关系。
像:a–>b–>c 属性之间含有这样的关系,是不符合第三范式的。
比如Student表(学号,姓名,年龄,性别,所在院校,院校地址,院校电话)
这样一个表结构,就存在上述关系。 学号–> 所在院校 --> (院校地址,院校电话)
这样的表结构,我们应该拆开来,如下。
(学号,姓名,年龄,性别,所在院校)–(所在院校,院校地址,院校电话)
总结
三大范式只是一般设计数据库的基本理念,可以建立冗余较小、结构合理的数据库。如果有特殊情况,当然要特殊对待,数据库设计最重要的是看需求跟性能,需求>性能>表结构。所以不能一味的去追求范式建立数据库。
作用就是角来:优化表的结构,减少数据的冗余
- 第一范式1NF:保证字段的值是最小单位不可再被分割
- 第二范式2NE:必须先保证遵循了第一范式,要求每张表都要有主键/主属性/主字段,
非主属性的值必须围绕主属性展开。 - 第三范式3NF:必须先保证遵循了第二范式,减少字段间的依赖传递