数据库(MySQL + Redis)

1.连接MySQL

连接:mysql -h 主机名 -P 端口 -u 用户名 -p密码
启动服务:net start mysql
关闭服务:net stop mysql

2.SQL语句分类

DDL(Data Definition Language)数据库定义语言
DCL(Data Control Language)数据库控制语言
DML(Data Manipulation Language)数据操纵语言
DQL(Data Query Language)数据查询语言

3.数据类型

数值类型
bit(位数):默认1,范围1~64,按位显示
tinyint [unsigned]:占1个字节
smallint:2
mediumint:3
int:4
bigint:8

float:4
double:8
decimal(总长度,小数长度)

字符串类型:
char(0-255):总是占用0~255固定长度的字节,查询速度>varchar
varchar(0-21844字符):占用0~65535字节,1-3个字节用于记录大小,utf8下最大21844字符
text:文本0~2^16字节
longtext:0~2^32

日期类型:
date
datetime
timestamp:deafult current_timestamp onupdate current_timestamp自动更新

4.库操作

新建:

create database [if not exists] 库名 character set 字符集 collate 校对规则 
#校对规则:utf8_bin 区分大小写 utf8_general_ci 不区分大小写(默认)

删除:

drop database [if exists] 库名

查询:

show databases

备份(DOS):
mysqldump -u 用户名 -p密码 -B 数据库1 数据库2 数据库n > d:\\文件名,sql
恢复(DOS):
Source 文件名.sql

5.表操作

切换数据库:

use 库名

新建:

create table 表名(字段名 字段类型(字段长度),字段2,字段3...) engine = 引擎

删除:

drop table 表名;

修改:

alter table 表名 add/modify/drop column 字段名 字段类型(字段长度)
alter table 表名 character set 字符集
rename table 表名 to 新表名

查看:

show tables;
desc 表名;

6 表记录操作

插入(必须一一对应,不写列名默认所有):

insert into 表名 [(列1, 列2, 列3...)] values (字段1的值, 字段2的值, 字段3的值...),(),()

删除:

delete from 表名 where

修改:

update 表名 set 字段名=字段的值 where 条件;

查询:

select 字段名 from 表名;

7 字段约束

主键(非空+唯一)+自动递增:

#设置了自增后,可以直接给null
create table 表名(字段名 字段类型(字段长度) primary key auto_increment)

非空+唯一(可以有多行null):

create table 表名(字段名 字段类型(字段长度) not null unique)

外键约束:
关联id字段为例,子表id来自主表id,要删除主表,需要先删除关联的子表

create table tb_user(
	id int primary key auto_increment,
	name varchar(20),
)
create table tb_user_addr(
	user_id int primary key auto_increment,
	addr varchar(200) ,
    #外键(子表的字段) 参考 主表名称(主表的字段)
	foreign key(user_id) references tb_user(id)
)

默认约束

create table 表名(sex char(3) default '男')

检查约束

#只做语法校验,不会生效
create table 表名(age int check(age>0 and age<=150))

8.常用函数

8.1 聚合函数

count:

select count(*)  from test;(包括对null的统计)
select count(1) from test;(包括对null的统计)
select count(字段名) from test;(不包括对null的统计)

max/min/sum/avg:

select max(num),min(num),sum(num),avg(num) from test

8.2 字符串函数

charset-返回字符串字符集
lower–转小写-select dname,lower(dname) from test
upper–转大写
length–求长度
concat–拼接串
substr(str, 位置, 长度)–截取字符串
replace(str, search_str, replace_str)–替换字符串
ltrim/rtrim/trim-去除左/右/两端空格

8.3 数学函数

abs-绝对值
bin-十进制转二进制
hex-十进制转十六进制
conv(数字,原来的进制,要转换的进制)-进制转换
round–对小数四舍五入
ceil–对小数向上取整
floor–对小数向下取整
format(数字,要保留的位数)-保留小数位数
mod-求余数
rand(seed)-返回[0,1]随机数,如果指定seed,则一旦生成不再改变

8.4 时间日期函数

current_date-当前日期
current_time-当前时间
current_timestamp-当前时间戳

date(datetime)-返回datetime的日期部分
date_add(date, 要加的时间带单位)
date_sub(date, 要减的时间带单位)
datediff(date1, date2)-差多少天
timediff(date1,date2)-差多少小时多少分钟多少秒

now–获取当前的年月日时分秒 year/mont/day/hour/minute/second

select now(),year(now()),month(now()),day(now())

unix_timestamp-返回1970-1-1到现在的秒数
from_unixtime(数字, ‘%Y-%m-%d %H:%i:%s’)-将秒数转换成时间戳

8.5 加密和系统函数

user-查询用户
datebase-数据库名称
md5-128位加密,32个十六进制
password-mysql用户密码加密

8.6 流程控制函数

if(表达式1, 结果1, 结果2)
ifnull(字段名,要替换的值)

9 条件查询

顺序:select … from … where … group by … having … order by … limit …

distinct-去重
select distinct dname from test

as-起名
select count(*) as ‘总数’ from test;

where-限定范围,常用的关键字有
and/or-与/或-select * from test where id=1 and/or dname=‘王小明’
like-模糊查询-select * from test where dname like ‘%小_’
is not null-空或非空-select * from test where dname is not null
between and-两端包含-select * from test where num between 1 and 10
in(值1,值2)-相当于or

limit 从哪行开始(从0开始计算), 取出多少行
公式:(当前页-1)*每页条数,每页条数
select * from test limit 2, 5

order by-排序
select * from test order by num(默认升序)
select * from test order by num desc(降序)

group by-分组
having-分组后进一步过滤
select avg(sal),id from emp group by id having avg(sal)>10000(这里只能用having,where不能出现聚合函数)

10 多表查询

10.1 笛卡尔积/内连接

笛卡尔积指的是将左边的表的每一条数据与右边的表的所有数据进行组合
可根据条件筛选出想要的数据,这种查询方式称为内连接

select * from dept a,emp b;#出现笛卡尔积
select * from dept a [inner] join emp b on a.deptno=b.deptno;#内连接

10.2 自连接

把一张表当做两张表使用,需要给表取别名

select worker.ename boos.ename from emp worker,emp boss where worker.mgr = boos.empno;#查询公司员工名字和他的上级的名字

10.3 子查询

嵌入在其他sql语句中的select语句

select * from emp where deptno = (select deptno from emp where ename = 'smith');#查询与smith同一部门的所有员工

10.3.1 all和any操作符

select ename, sal, deptno from emp where sal > all(select sal from emp where deptno = 30);#显示工资比30部门的所有的员工的工资高的员工信息(同max)

select ename, sal, deptno from emp where sal > any(select sal from emp where deptno = 30);#显示工资比30部门的其中一个员工的工资高的员工信息(同min)

10.3.3 表复制

去重my_tab的记录

create table my_temp like my_tab;#创建结构一样的临时表
insert into my_temp select distinct * from my_tab;#去重后复制到临时表
delete from my_tab;#删除记录
insert into my_tab select * from my_temp;#复制临时表的记录
drop table my_tmp;#删除临时表

10.4 外连接

左外连接:左侧的表完全显示
右外连接:右侧的表完全显示

select * from dept a left/right join emp b where a.deptno = b.deptno;

10.5 union/union all

将两个查询结果合并为一个,要求两个语句字段个数和类型一直

区别:union会去掉重复的记录且按照字段顺序进行排序,union all不会

11 存储过程和函数

事先经过编译并存储在数据库中的一段 SQL 语句的集合
存储过程没有返回值,函数必须有返回值

11.1 存储过程基本操作

创建存储过程:

--修改分隔符为$
delimiter $

create procedure 存储过程名称(参数名 数据类型)
begin
	sql语句;
end$

-- 修改分隔符为;
delimiter ;

调用存储过程:

call 存储过程名称(实际参数);

查看存储过程:

selete * from mysql.proc where db = '数据库名称'

删除存储过程:

drop procedure [if exists] 存储过程名称;

11.2 存储过程语法

#定义变量
declare 变量名 数据类型 [default 默认值];
#变量赋值
set 变量名 = 变量值;
select 列名 into 变量名 from 表名 where 条件;

#if语句
if total >= 380 then 
	set description = '优秀';
elseif total >= 320 and total < 380 then
	set description = '良好';
else 
	set description = '一般';
end if;
select description;

#输入/输出参数
create procedure 存储过程名称(in/out/inout 参数名 数据类型)
#传入参数,变量名称加上@代表用户会话变量,加上@@代表系统变量
call pro_test((select sum(score) from student),@description);

#case语句
case
when total >= 380 then
	set description = '优秀';
when total >= 320 and total < 380 then
	set description = '良好';
else
	set description = '一般';
end case;
select description;

#while语句
while num <= 100 do
	if num%2 = 0 then
		set result = result + num;
	end if;
	set num = num + 1;
end while;
select result;

11.3 存储函数

与存储过程相似,有返回值

创建存储函数:

delimiter $

create function 函数名称(参数名 数据类型)
returns 返回值类型
begin
	sql语句;
	return 结果;
end$

delimiter ;

调用存储函数:

select 函数名(实际参数);

删除存储函数:

drop function 函数名称;

12 触发器

可以在insert/update/delete之前或之后,触发并执行触发器中定义的SQL语句,可以使用new和old来引用触发器中发生变化的内容

触发器类型 OLD的含义 NEW的含义
INSERT 型触发器 无 (因为插入前状态无数据) NEW 表示将要或者已经新增的数据
UPDATE 型触发器 OLD 表示修改之前的数据 NEW 表示将要或已经修改后的数据
DELETE 型触发器 OLD 表示将要或者已经删除的数据 无 (因为删除后状态无数据)

创建触发器:

delimiter $

create trigger 触发器名称
before/after insert/update/delete
on 表名
[for each row] --代表行级触发器
begin
	sql语句;
	insert into log values(null,'update',now(),old.meney,new.money)
end$

delimiter ;

查看触发器:

show triggers;

删除触发器:

drop trigger 触发器名称;

13 视图

将一些比较复杂的查询语句封装到一个虚拟表中

视图的数据变化会影响到基表,基表的数据变化也会影响到视图

#创建
create view 视图名称 as 查询语句;
#查询
select * from 视图名称;

14 事务

能保证多条SQL要么全成功要么全失败

14.1 事务的基本操作

#开启事务
start transaction;
#设置保存点
savepoint 保存点名;
#回退事务到保存点
rollback to 保存点名
#回退全部事务
rollback
#提交事务,操作生效
commit;

14.2 事务提交方式

mysql默认自动提交事务

#查看提交方式
select @@AUTOCOMMIT; --1代表自动提交,0代表手动提交
#修改提交方式
set @@AUTOCOMMIT = 1/0;

14.3 事务的四大特征(ACID)

原子性(Atomicity):多个SQL处于同一个事务里,要么全成功要么全失败
一致性(Consistency):让数据保持逻辑上的“合理性”,比如:一个商品出库时,既要让商品库中的该商品数量减1,又要让对应用户的购物车中的该商品加1
隔离性(Isolation):数据库支持并发访问,保证事务间是隔离的,互不影响
持久性(Durability):一旦提交事务,对数据库的操作是永久的

14.4 事务的隔离级别

脏读:
一个事务读取到了另一个未提交的事务中的数据

不可重复读:
一个事务读取到了另一个事务中修改或删除并提交的数据

幻读:
一个事务查询到了另一个事务插入并提交的新数据

隔离级别 名称 脏读 不可重复读 幻读 备注
1 read uncommitted 读未提交 可读未提交事务的数据
2 read committed 读已提交 通过MVVC实现,每次快照读都会生成最新的快照
3 repeatable read 可重复读 InnoDB默认,通过MVCC和锁实现,第一次快照读生成快照,一直使用同一个
4 serializable 串行化 读的时候加共享锁,写的时候加排它锁
##修改数据库隔离级别
set global transaction isolation level 级别;
##查看数据库隔离级别
select @@TX_ISOLATION;

14.4.1 幻读

在默认隔离级别下

快照读:指的是单纯的select操作,这种场景下基于MVVC避免幻读

当前读:加锁查询和DML,这种场景下引入间隙锁解决幻读,间隙锁确保无法插入新的记录,行锁和间隙锁共同组成临键锁(next-key locks)

原因:行锁只能锁住行,即使把所有的行记录都上锁,也阻止不了新插入的记录

原则:
加锁的基本单位是next-key lock,next-key lock是前开后闭区间
锁是加在索引上的,查找过程中访问到的索引才会加锁,如果查询使用覆盖索引,不需要访问主键索引的话就不会给主键索引加锁

优化:
对唯一索引的等值查询,如果值已经存在,不可能插入其他等值的数据,只加行锁
对于非唯一索引等值查询,因为不能保证值唯一,所以会从等值查询的值开始,向右遍历到第一个不满足等值条件记录结束,然后将不满足条件记录的next-key退化为间隙锁

14.5 MVVC

MVCC多版本并发控制基于乐观锁理论,用于实现读已提交和可重复读取隔离级别。

InnoDB 的 MVCC,是通过在每行记录后面保存两个隐藏的列来实现。这两个列,一个保存了行的创建时间,一个保存行的过期时间(删除时间)。当然存储的并不是真实的时间,而是系统版本号(system version number)。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。

在可重复读级别下:

  • SELECT:InnoDB只查找版本早于当前事务版本的数据行,这样可以确保事务读取的行,要么是在开始事务之前已经存在要么是事务自身插入或者修改过的,行的删除版本号要么未定义,要么大于当前事务版本号,这样可以确保事务读取到的行在事务开始之前未被删除,只有符合上述两个条件的才会被查询出来

  • INSERT:InnoDB为新插入的每一行保存当前系统版本号作为行版本号

  • DELETE:InnoDB为删除的每一行保存当前系统版本号作为行删除标识

  • UPDATE:InnoDB为插入的一行新纪录保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为删除标识

15 引擎

15.1 常见引擎的特性

MySQL5.7支持的引擎包括:InnoDB、MyISAM、MEMORY、Archive、Federate、CSV、BLACKHOLE等
其中较为常用的有三种:InnoDB、MyISAM、MEMORY

特性 MyISAM InnoDB MEMORY
存储限制 有(平台对文件系统大小的限制) 64TB 有(平台的内存限制)
事务安全 不支持 支持 不支持
锁机制 表锁 表锁/行锁 表锁
B+Tree索引 支持 支持 支持
哈希索引 不支持 不支持 支持
全文索引 支持 支持 不支持
集群索引 不支持 支持 不支持
数据索引 不支持 支持 支持
数据缓存 不支持 支持 N/A
索引缓存 支持 支持 N/A
数据可压缩 支持 不支持 不支持
空间使用 N/A
内存使用 中等
批量插入速度
外键 不支持 支持 不支持

15.2 引擎的操作

#查询数据库支持的引擎
show engines;
#查询某个数据库中数据表的引擎
show table status from 数据库名称 where name = '表名'

15.3 引擎的选择

  • MyISAM :MyISAM不支持事务、不支持外键、支持全文检索和表级锁定.写操作时会锁住整张表,导致其他查询和更新都会被阻塞,因此并发访问受限。所以如果是以读为主,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。
  • InnoDB : 是MySQL的默认存储引擎, 由于InnoDB支持事务、支持外键、行级锁定 ,支持所有辅助索引(5.6前不支持全文检索),高缓存,所以用于对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,读写频繁的操作,那么InnoDB存储引擎是比较合适的选择,比如BBS、计费系统、充值转账等
  • MEMORY:将所有数据保存在RAM中,在需要快速定位记录和其他类似数据环境下,可以提供更快的访问。MEMORY的缺陷就是对表的大小有限制,太大的表无法缓存在内存中,其次是要确保表的数据可以恢复,数据库异常终止后表中的数据是可以恢复的。MEMORY表通常用于更新不太频繁的小表,用以快速得到访问结果。

16 索引

提高查找效率的数据结构

16.1 索引的分类

16.1.1 逻辑角度

  • 普通索引:最基本的索引,它没有任何限制。

  • 唯一索引:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值组合必须唯一。唯一约束自动创建

  • 主键索引:一种特殊的唯一索引,不允许有空值。主键自动创建

  • 组合索引:将单列索引进行组合。

  • 外键索引:只有InnoDB引擎支持外键索引,用来保证数据的一致性、完整性和实现级联操作.外键约束自动创建

  • 全文索引:快速匹配全部文档的方式

16.1.2 数据结构角度

  • B+树索引:mysql默认
  • Hash索引:只适用于等值查询,对范围查找需要全表扫描
  • Full-Text全文索引
  • R-Tree索引

16.1.3 物理存储角度

  • 聚集索引
  • 非聚集索引

16.2 索引的操作

#创建索引,默认索引类型是B+Tree类型
create [unique/fulltext] index 索引名称
[useing 索引类型] on 表名(列名);

#alter语句添加索引
alter table 表名 add [unique/fulltext] index 索引名称(列名);
alter table 表名 add primary key(列名);--特殊,添加主键索引

#查看索引
show index from 表名;

#删除索引
drop index 索引名称 on 表名;
alter table 表名 drop index from 列名;

16.3 B+Tree树索引

磁盘存储:
系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。InnoDB 存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB 存储引擎中默认每个页的大小为16KB.而系统一个磁盘块的存储空间往往没有这么大,因此 InnoDB 每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小 16KB。InnoDB 在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘I/O次数,提高查询效率。

B+树的每个节点就是一个页,其中非叶子节点不存储数据,仅存储键值,这样做加大每个节点存储的key值数量,降低B+Tree的高度,减少磁盘IO次数,叶子节点存储了表中所有的数据并通过双向链表连接,使得范围查找方便

16.4 聚集索引和非聚集索引

16.4.1 聚集索引(InnoDB的主键索引):

定义:以主键的值作为键值构建的B+树索引

具体说明:
1.主键索引的B+树的非叶子节点存放主键,叶子节点储存行数据,辅助键索引B+树(非聚集索引)存储辅助键,叶子节点存储主键
2.若使用"where id = 14"这样的条件查找主键,则按照B+树的检索算法即可查找到对应的叶节点,之后获得行数据。若对Name列进行条件搜索,则需要两个步骤:第一步在辅助索引B+树中检索Name,到达其叶子节点获取对应的主键。第二步使用主键在主键索引B+树中再执行一次B+树检索操作,最终到达叶子节点即可获取整行数据(回表查询)。

细节:
1.在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主键索引。
2.因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。
3.和MyISAM不同,InnoDB的辅助索引数据域存储的也是相应记录主键的值而不是地址,所以当以辅助索引查找时,会先根据辅助索引找到主键,再根据主键索引找到实际的数据。所以Innodb不建议使用过长的主键,否则会使辅助索引变得过大。建议使用自增的字段作为主键,这样B+Tree的每一个结点都会被顺序的填满,而不会频繁的分裂调整,会有效的提升插入数据的效率。

优缺点:
1.主键id和行数据存储在一起,查询更快
2.可以把相关数据保存在一起,如:实现电子邮箱时,可以根据用户ID来聚集数据,这样只需要从磁盘读取少量的数据页就能获取某个用户全部邮件,如果没有使用聚集索引,则每封邮件都可能导致一次磁盘IO

1.提高了磁盘IO的性能,但如果数据全部放在内存中,聚集索引也没有什么优势了
2.更新聚簇索引列的代价很高,因为会强制InnoDB将每个被更新的行移动到新的位置。
3…基于聚簇索引的表在插入新行,或者主键被更新导致需要移动行的时候,可能面临“页分裂”的问题。当行的主键值要求必须将这一行插入到某个已满的页中时,存储引擎会将该页分裂成两个页面来容纳该行,这就是一次分裂操作。页分裂会导致表占用更多的磁盘空间。

16.4.2 非聚集索引(MyISM的索引):

定义:以主键以外的列值作为键值构建的B+树索引

具体说明:
主键索引B+树的节点存储了主键,辅助键索引B+树存储了辅助键。表数据存储在独立的地方,这两颗B+树的叶子节点都使用一个地址指向真正的行数据,对于表数据来说,这两个键没有任何差别(只是主索引要求key是唯一的,而辅助索引的key可以重复)。如果查询列中包含了该索引没有覆盖的列,那么还要进行第二次的查询,查询节点上对应的行数据

16.5 索引最左匹配原则

查询时条件为精确匹配(=/in),且是联合索引的左边连续一列或几列,就可以命中索引
如果用到了联合索引的所有列但是不遵循最左原则,查询优化器会重排序

16.6 覆盖索引

只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快。

实现方式:将被查询的字段,建立到联合索引里去

16.7 索引的设计原则

1.在较频繁查询的字段创建索引
2.唯一性太差的字段不适合单独创建索引,比如性别列
3.更新非常频繁的字段不适合创建索引

16.8 索引失效的场景

1.违反最左原则
2.在索引列上做计算,函数,类型转换等任何操作
3.使用不等于(!=,<>)
4.like以通配符开头(‘%abc’)
5.查询条件以or连接,指的是查询条件包含了非索引字段和索引字段
6.字符串不加单引号

17 锁

17.1 锁的分类

  • 按操作分类:
    • 共享锁:也叫读锁。针对同一份数据,多个事务读取操作可以同时加锁而不互相影响 ,但是不能修改数据记录。
    • 排他锁:也叫写锁。当前的操作没有完成前,会阻断其他操作的读取和写入
  • 按粒度分类:
    • 表级锁:操作时,会锁定整个表。开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低。(MyISAM存储引擎)
    • 行级锁:操作时,会锁定当前操作行。开销大,加锁慢;会出现死锁;锁定力度小,发生锁冲突的概率低,并发度高。(InnoDB存储引擎默认)
      • 行级锁的实现:记录锁,间隙锁,临键锁
    • 页级锁:锁的粒度、发生冲突的概率和加锁的开销介于表锁和行锁之间,会出现死锁,并发性能一般。
  • 按使用方式分类:
    • 悲观锁:每次查询数据时都认为别人会修改,很悲观,所以查询时加锁。
    • 乐观锁:每次查询数据时都认为别人不会修改,很乐观,但是更新时会判断一下在此期间别人有没有去更新这个数据.实现方式:先读取版本号,在更新的时候比较是否一致

InnoDB中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。

InnoDB引擎加锁基本单位是next-key lock,如果修改或查询时用的是不带索引的列会提升为表锁

17.2 InnoDB共享锁和排他锁(行锁)

#共享锁,可以被多个事务查询,但是不能修改
select * from student where id = 1 lock in share mode;

#排他锁,不能被其他事务加锁查询或修改
select * from student where id = 1 for update;

17.3 MyISAM共享锁和排他锁(表锁)

#读锁,只能读取,不能修改
lock table 表名 read;
#解锁
unlock tables;

#写锁,其他连接不能查询和修改
lock table 表名 write;
#解锁
unlock tables;

18 Mysql管理

18.1 Mysql用户管理

#创建用户,如果不指定登录位置,则为%,表示所有IP都有连接权限
create user '用户名'@'允许登录的位置' identified by '密码';
#删除用户
drop user '用户名'@'允许登录的位置';
#修改自己的密码
set password = password('密码');
#修改他人的密码(需要权限)
set password for '用户名'@'允许登录的位置' = password('密码');

18.2 Mysql权限管理

#赋予权限,权限列表grant select, delete, create on ...
grant 权限列表 on 库.数据对象名 to '用户名'@'允许登录的位置' [identified by '密码'];
#回收权限
revoke 权限列表 on 库.数据对象名 from '用户名'@'允许登录的位置';
#刷新权限
flush privileges;

19 JDBC和连接池

19.1 JDBC

Driver:驱动类,加载类时底层静态代码块自动注册驱动,msql5以上可以不加载类,根据meta-inf自动注册,建议写上
DriverManager:管理驱动对象,用于注册驱动和获取连接
Connection:数据库连接对象
Statement:用于执行静态SQL语句并返回其生成结果,存在SQL注入问题
PreparedStatement:预编译SQL语句
CallableStatement:调用存储过程
ResultSet:结果集,指针指向表头

public int executeUpdate();//执行DML语句,返回影响的行数
public ResultSet executeQuery();//执行DQL语句,返回结果集
public boolean execute();//执行任意SQL语句,如果返回结果集则为true
connection.setAutoCommit(false);//取消自动提交事务
connection.commit();//提交事务
connection.rollback();//回滚事务,可在异常发生后用在catch块中

19.2 实现JDBC工具类

使用JDBC工具类:

public class Use_JDBCUtils {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            conn = JDBCUtils.getConnection();
            ps = conn.prepareStatement("delete from user1 where id=?");
            ps.setInt(1, 3);
            ps.executeUpdate();//DML返回影响的行数,不写SQL
            /*
            st = conn.createStatement();
            resultSet = st.executeQuery("select * from emp");//DQL返回结果集
            while(resultSet.next()) {
                int id = resultSet.getInt("id");
                String name  = result.getString("name");
                Data date = resultSet.getDate("date");
            }
            */
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.close(null, ps, conn);
        }
    }
}

封装JDBC工具类:

/**
 * JDBC工具类
 */
public class JDBCUtils {
    private static String user;
    private static String password;
    private static String url;
    private static String driver;

    static {
        try {
            Properties properties = new Properties();
            properties.load(new FileInputStream("src\\mysql.properties"));
            user = properties.getProperty("user");
            password = properties.getProperty("password");
            url = properties.getProperty("url");
            driver = properties.getProperty("driver");
        } catch (IOException e) {
            //将编译异常转为运行时异常,调用者可以捕获也可以默认处理
            throw new RuntimeException(e);
        }
    }

    /**
     *注册驱动,返回连接,不建议像下面这样写死
     *Class.forName("com.mysql.cj.jdbc.Driver");
     *String url = "jdbc:mysql://localhost:3306/mytest";
     *Connection connection = DriverManager.getConnection(url, "root", "721211");
     */
    public static Connection getConnection() throws Exception {
        return DriverManager.getConnection(url, user, password);
    }

    /**
     * 既可以传入Statement对象,也可以传入PrepareStatement对象
     * 如果不需要关闭,则传入null
     */
    public static void close(ResultSet rs, Statement st, Connection conn) {
        try {
            if (rs != null) {
                rs.close();
            }
            if (st != null) {
                st.close();
            }
            if (conn != null) {
                conn.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

19.3 数据库连接池

19.3.1 C3PO连接池

ComboPooledDataSource = new ComboPooledDataSource("xml配置文件的名称");
Connection connection = comboPooledDataSource.getConnection();

19.3.2 Druid连接池

Properties properties = new Properties();
properties.load(new FileInputStream("src\\druid.properties"));
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection connrection = dataSource.getConnection();

2 Redis

2.1 非关系型数据库

NoSQL(Not Only SQL),意即“不仅仅是SQL”,泛指非关系型的数据库,通常使用K-V结构在内存中存储数据,用于高并发下海量数据的读写

Redis就属于这种数据库,使用单线程+多路IO复用技术

2.2 Redis的安装与启动

安装

yum intsall gcc #安装C语言运行环境
tar -zxvf redis-6.2.1.tar.gz #安装包放/opt目录,解压Redis安装包
cd redis-6.2.1 #进入目录
make #编译
make intsall #安装,在/usr/local/bin

后台启动,将redis.conf文件里面的daemonize no 改成 yes

redis-server /etc/redis.conf #后台启动,指定配置文件
redis-cli #客户端访问
redis-cli shutdown #关闭redis

2.3 数据类型(value的)及命令

关于key和库的命令:

keys * 查看当前库所有key

exists 判断某个key是否存在

type 查看你的key是什么类型

del 删除指定的key数据

unlink 真正的删除会在后续异步操作

expire

ttl 查看还有多少秒过期,-1表示永不过期,-2表示已过期

select 切换数据库

dbsize 查看当前数据库的key的数量

flushdb 清空当前库

flushall 清空全部库

2.3.1 String字符串

数据结构:
简单动态字符串,类似ArrayList,内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。
当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M。

常用命令:

set <key> <value> #添加键值对
setnx <key> <value> #只有在key不存在时,设置 key 的值
setex <key> <过期时间> <value> #设置键值的同时,设置过期时间,单位秒
get <key> #查询对应键值
mset/msetnx/mget <key1> <value1> <key2> <value2>... #同时设置/获取一个或多个 key-value对
append <key> <value> #将给定的value追加到原值的末尾
strlen <key> #获得值的长度
incr/decr <key> #将key中储存的数字值增/减1
incrby/decrby <key> <步长> #将key中储存的数字值增/减,自定义步长。

2.3.2 List列表

数据结构:

在数据量少的情况下会使用一块连续的内存存储,这个结构是ziplist压缩列表
在数据量多的情况下使用双向指针将多个ziplist连接成quicklist快速链表

常用命令:

lpush/rpush <key> <value1> <value2> <value3>... #从左边/右边插入一个或多个值,头插法
lpop/rpop <key> #从左边/右边吐出一个值
rpoplpush <key1> <key2> #从key1列表右边吐出一个值,插到key2列表左边
lrange <key> <start> <stop> #按照索引下标获得元素(从左到右),0表示左边第一个,-1右边第一个
lindex <key> <index> #按照索引下标获得元素(从左到右)
llen <key> #获得列表长度 
linsert <key> before <value> <newvalue> #在value的后面插入值
lrem <key><n><value> #从左边删除n个value
lset <key> <index> <value> #将列表key下标为index的值替换成value 

2.3.3 Set集合

数据结构:

哈希表

常用命令:

sadd <key> <value1> <value2>... #将一个或多个元素加入到集合中,已经存在的元素将被忽略
smembers <key> #取出该集合的所有值
sismember <key> <value> #判断集合是否为含有该值,有1,没有0
srem <key> <value1> <value2> #删除集合中的某个元素
scard <key> #返回该集合的元素个数

2.3.4 Hash哈希表

数据结构:

哈希表

常用命令:

hset <key> <field> <value> #给field键赋值value
hget <key1> <field> <key1> #从field取出value 
hmset <key1> <field1> <value1> <field2> <value2>... #批量设置hash的值
hexists <key1> <field> #查看field是否存在。 
hkeys <key> #列出该hash集合的所有field
hvals <key> #列出该hash集合的所有value

2.3.5 Zset有序集合(Sorted Set)

数据结构:

1.每个String类型的Value都关联一个Double类型的评分Score,Value不能重复,Score可以重复,这部分使用哈希表
2.对Value的实现还会使用跳表,跳表的每个节点都包含Value(哈希表的key)并传入对应的Score,按照Score形成有序链表排序

跳表:

对有序单链表每几个节点就提取一个节点出来作为索引放在上一层形成链表,查找时从最上层开始查找

常用命令:

zadd <key> <score1> <value1> <score2> <value2>... #将一个或多个元素加入
zrange <key> <start> <stop> [withscores] #返回有序集中,顺序在之间的元素
zrem  <key> <value> #删除该集合下,指定值的元素 
zcount <key> <min> <max> #统计该集合,分数区间内的元素个数 
zrank <key> <value> #返回该值在集合中的排名,从0开始

2.4 配置文件

自定义目录: /etc/redis.conf

默认情况bind=127.0.0.1只能接受本机的访问请求,需要注释掉

将本机访问保护模式protected-mode设置为no

2.5 持久化

2.5.1 RDB(Redis DataBase)

在指定的时间间隔内将内存中的数据集快照写入磁盘

执行过程(bgsave):

  • Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件,redis重启时会自动加载安装目录下的dump.rdb数据至内存

触发方式:

  • 使用save(会阻塞)或bgsave(推荐,异步)手动保存
  • 在redis.conf配置文件中的SNAPSHOTTING下配置save 300 10,表示300 秒内如果至少有 10 个 key 的值变化则保存,自动触发bgsave

优缺点:

  • 优点是整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。
  • 缺点是最后一次持久化后的数据可能丢失(还没持久化服务器就挂了)
# RDB自动持久化规则
# 当 900 秒内有至少有 1 个键被改动时,自动进行数据集保存操作
save 900 1
# 当 300 秒内有至少有 10 个键被改动时,自动进行数据集保存操作
save 300 10
# 当 60 秒内有至少有 10000 个键被改动时,自动进行数据集保存操作
save 60 10000

# RDB持久化文件名
dbfilename dump-<port>.rdb

# 数据持久化文件存储目录
dir /var/lib/redis

# bgsave发生错误时是否停止写入,通常为yes
stop-writes-on-bgsave-error yes

# rdb文件是否使用压缩格式
rdbcompression yes

# 是否对rdb文件进行校验和检验,通常为yes
rdbchecksum yes

2.5.2 AOF(Append Only File)

以日志的形式来记录每个写操作

执行过程:

  1. 客户端的请求写命令会被append追加到AOF缓冲区内
  2. AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中
  3. AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量,重写就是AOF文件持续增长而过大时,会把RDB的快照以二级制的形式附在新的aof头部,作为已有的历史数据,替换掉原来的流水账操作
  4. Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的

触发方式:

  • 默认不开启,redis.conf中配置appendonly yes,AOF和RDB都开启时,系统默认读取AOF的数据

优缺点:

  • 优点是丢失数据概率更低,且因为记录了每个写操作,可以处理误操作
  • 缺点是占用空间大,恢复数据慢,且如果每次写操作都同步,性能有损耗
# 开启AOF持久化方式
appendonly yes

# AOF持久化文件名
appendfilename appendonly-<port>.aof

# 每秒把缓冲区的数据同步到磁盘
appendfsync everysec

# 数据持久化文件存储目录
dir /var/lib/redis

# 是否在执行重写时不同步数据到AOF文件,这两者都会操作磁盘,影响性能
# 这里的 yes,就是执行重写时不同步数据到AOF文件,有可能会丢失数据
no-appendfsync-on-rewrite yes

# 触发AOF文件执行重写的最小尺寸
auto-aof-rewrite-min-size 64mb

# 触发AOF文件执行重写的增长率(这里100%就是两倍)
auto-aof-rewrite-percentage 100

2.6 Jedis

准备工作:

1.禁用Linux防火墙systemctl stop/disable firewalld.service
2.redis.conf中注释掉bind 127.0.0.1 ,且protected-mode no

导包

<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.2.0version>
dependency>

使用

public class Demo01 {
public static void main(String[] args) {
    Jedis jedis = new Jedis("192.168.137.3",6379);
    //jedis.XXX,与命令相同的API
    jedis.close();
    }
}

2.7 RedisTemplate

Spring-data-redis使用RedisTemplate来实现对Redis的访问操作

将Jedis的同一类型操作封装为operation接口

  • ValueOperations:简单K-V操作
  • SetOperations:set类型数据操作
  • ZSetOperations:zset类型数据操作
  • HashOperations:针对map类型的数据操作
  • ListOperations:针对list类型的数据操作

提供了对key的“bound”(绑定)便捷化操作API,可以通过bound封装指定的key,然后进行一系列的操作而无须“显式”的再次指定Key,即BoundKeyOperations

  • BoundValueOperations
  • BoundSetOperations
  • BoundListOperations
  • BoundSetOperations
  • BoundHashOperations

2.8 缓存问题及解决方案(读+写)

2.8.1 缓存穿透

查询缓存和数据库中都不会存在的数据,如id为“-1”的数据或id为特别大不存在的数据,相当于缓存层失去意义,每次都请求数据库

解决方案:

  1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
  2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击(如果用户用不同的id攻击呢)

2.8.2 缓存击穿

某一条热点数据缓存时间到期,此时高并发的请求进来会导致数据库压力增大,强调并发

解决方案:

  1. 设置热点数据永远不过期
  2. 加互斥锁

2.8.3 缓存雪崩

缓存中数据大批量同时过期,与缓存击穿不同的是大量数据都过期了

解决方案:

  1. 缓存数据的过期时间设置随机
  2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中
  3. 设置热点数据永远不过期

2.8.4 缓存一致性(写)

如何保证缓存中的数据和数据库中的数据一致

  1. 双写模式:更新数据库后更新缓存
  2. 失效模式:更新数据库后删除缓存
  3. 加读写锁

两种模式在并发状态下都会出现问题,但只要设置缓存的过期时间,保证最终一致性即可

面试题

1 SQL语句的执行流程是?

客户端请求 -> 连接器(连接层,验证用户身份,给予权限) ->查询缓存(存在缓存则直接返回) -> 分析器(对SQL语句进行语法分析) -> 优化器(选择索引,优化SQL语句) ->执行器(有执行权限才去使用这个引擎提供的接口,到这里都属于服务层) -> 引擎层获取储存层的数据返回

2 哪个存储引擎执行 select count(*) 更快,为什么?

MyISAM更快,MyISAM把表的总行数存储在磁盘上,而InnoDB需要全表扫描

3 数据库设计三大范式

1.确保每列保持原子性,即每一列属性都是不可再分的属性值

2.在第一范式的基础上,确保每列完全依赖于全部主键而非部分主键,主要针对联合主键而言,如订单表将订单编号和商品编号作为联合主键,其中商品信息只与商品编号相关,应该分开

3.在第二范式的基础上确保每列都和主键直接相关,而不是间接相关,比如在设计一个订单表的时候,可以将客户编号作为一个外键和订单表建立相应的关系。而不可以在订单表中添加关于客户其它信息

4 怎么处理MySQL慢查询

1.开启慢查询日志(如查询时间>1s),定位哪个SQL语句出了问题
2.分析sql语句是否查询了多余的数据,是否使用了索引进行优化
3.如果无法优化,可以考虑表中的数据量是否太大,进行垂直/水平分库分表

你可能感兴趣的:(数据库,mysql,redis)