数据库 常见概念汇总
超键(super key):在关系中能唯一标识元素属性的集称为关系模式的超键。
候选键:(Candidate Key):不含有多余属性的超键称为候选键。也就是说在候选键中在删除属性,就不是键了。
主键(Primary Key):主键就是候选键里面的一个,是人为规定的。
外键(Froeign Key):如果模式R中的属性k是其他模式的主键,那么k在模式R中称为外键。
主属性:如果A是关系模式R的候选键的一个属性,则称A是R的主属性,否则称A是R的非主属性。
数据库设计三范式
1NF:属性项不可分
2NF:R中的每一个非主属性,完全依赖于R的某一个候选键
3NF:R中的每一个非主属性都不传递依赖于R的候选键
BCNF:R满足1NF,且每个属性都不传递依赖于R的候选键
SQL select 语句执行顺序
- from 子句组装来自不同数据源的数据;
- where子句基于指定的条件对记录行进行筛选;
- group by子句将数据划分为多个分组;(每个分组只能输出一行)
- 使用聚集函数进行计算;
- 使用having 子句筛选分组;
- 计算所有的表达式;
- select 的字段;
- 使用 order by 对结果进行排序。
每个步骤都会产生一个虚拟表用作下一个步骤的输入。
MySQL 性能优化
对索引的优化(防止索引失效)
● 最佳左前缀法则 :如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
● 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少 select *
● 建立复合索引时,把选择性更高的字段放到索引的前面,这样通过第一个字段就可以过滤掉大多数不符合条件的数据。
● 删除长期未使用的索引(索引本身要占空间)
● 不在索引列上做任何操作(计算、函数、类型转换),会导致索引失效而转向全表扫描
● 索引中范围条件(>、<、between、in)右边的列索引会失效
● mysql中使用不等于(!=或<>)的时候无法使用索引,导致全表扫描
● is null , is not null 也无法使用索引
● like 以通配符开头(‘%ab…’)mysql 索引会失效,变成全表扫描
● 少用 or ,用它连接时会索引失效
● 字符串不加单引号索引失效 ,varchar不加单引号是重罪。-
order by 关键字优化 (尽量使用 Index 方式排序,避免使用 filesort 方式排序)
● order by 语句索引适用最佳左前缀法则,或where 子句与 order by 子句条件组合满足索引最佳左前缀法则,反之索引失效
● 除了索引的影响,还需考虑:- 不要使用 select * ,只使用查询需要的字段,影响如下:
1.1 、当query 字段大小总和小于 max_length_for_sort_data ,而且排序字段不是 TEXT|BLOB 类型时,会用单路排序,否则会用老的多路排序。
1.2、 两种算法的数据都有可能超出 sort_buffer 的容量,超出之后,会创建 temp 文件进行排序,导致多次IO ,但是用单路排序算法风险更大(一次读取全部数据,占空间多),所以要提高 sort_buffer_size。 - 尝试提高 sort_buffer_size
不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的 - 尝试提高 max_length_for_sort_data
提高这个参数,会增加用改进算法即单路排序的概率。但是如果设的太高,数据总容量超出 sort_buffer_size的概率就增大,明显症状是高的磁盘IO活动和低的处理器使用率。
- 不要使用 select * ,只使用查询需要的字段,影响如下:
group by 关键字优化
group by 实质是先排序后进行分组,遵循索引的最佳左前缀法则。
当无法使用索引列,增大 max_length_for_sort_data 和 sort_buffer _size 参数设置。
以上两点同 order by优化 COUNT() 查询。如果要统计行数,直接使用 COUNT(*),意义清晰,且性能更好。count(某列)不会统计null。
优化 UNION
除非确实需要服务器去重,否则就一定要使用 UNION ALL,如果没有 ALL 关键字,MySQL 会给临时表加上 DISTINCT 选项,这会导致整个临时表的数据做唯一性检查,这样做的代价非常高。当只要一行数据时使用 limit 1
查询时如果已知会得到一条数据,加上 limit 1 。因为mysql数据库引擎会在找到一条结果停止搜索,不会继续查询下一条是否符合标准直到查询完所有记录。limit的基数比较大时使用between
例如:select * from admin order by admin_id limit 100000,10;
优化为:select * from admin where admin_id between 100000 and 100010 order by admin_id;
Between 限定比Limit快,所以在海量数据访问时,建议用Between 或是 where 替换掉 limit。
但是between 也有缺陷,如果id中间有断行或是中间部分id不读取的情况,总读取量可能小于预计读取量(有损服务)。
尽可能的使用覆盖索引扫描,而不是查询所有的列。先获取需要访问的记录后在根据关联列回原表查询所需要的列。
SELECT film_id,description FROM film ORDER BY title LIMIT 50,5;
如果这张表非常大,那么这个查询最好改成下面的样子:
SELECT film.film_id,film.description
FROM film INNER JOIN (
SELECT film_id FROM film ORDER BY title LIMIT 50,5
) AS tmp USING(film_id);永远小表驱动大表
因为先查小表可以得到一些接下来查询的过滤条件,再查大表时可以根据这些过滤条件用上索引等内容增加整体查询速度。
主表比子表大用 IN ,相反用 EXISTS;
因为 IN是子表驱动主表,EXISTS是主表驱动子表。select * from admin left join log on admin.admin_id=log.admin_id where log.admin_id>10 如何优化?
select * from (select * from admin where admin_id>10)T1 left join log on T1.admin_id = log.admin_id. 使用join时候,应该 用小的结果驱动大的结果(left join左边表结果尽量小如果有条件应该放到左边先处理,right join 同理相反),同时尽量把牵涉到多表联合的查询拆分多个query(多个连表查询效率低,容易到之后锁表和阻塞)。仅列出需要查询的字段,这对速度不会有明显的影响,主要考虑节省内存。 减轻网络流量。MySQL 的客户端/服务端通信协议是“半双工”的。
SELECT * FROM tabel; 优化为 SELECT id,name,age FROM table;选择正确的数据库引擎,MyISAM 和 InnoDB
MyISAM:适用于大量查询的应用,对大量写功能的应用不是很好。对 select count(*) 操作快,MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可。不支持事务、外键和行级锁。
InnoDB:支持事务,支持外键。不支持全文索引。支持行锁,在写操作比较多的时候会比较优秀。MySQL5.5版本开始InnoDB已经成为Mysql的默认引擎不要在数据库中保留null,尽量使用not null 填充数据库。
空值是不占用空间的,NULL其实是占用空间的. mysql在进行比较的时候,NULL 会参与字段比较,所以对效率有一部分影响。索引不会存储NULL值,所以如果索引的字段可以为NULL,索引的效率会下降很多。Scheme 设计与数据类型优化
选择数据类型要遵循小而简单的原则,越小的数据类型通常会更快,占用更少的磁盘、内存, CPU 资源。比如,整型就比字符操作代价低,因而会使用整型来存储 ip 地址,使用 DATETIME 来存储时间,而不是使用字符串。
当存储IPv4地址时,应该使用32位的无符号整数(UNSIGNED INT)来存储IP地址,而不是使用字符串。MySQL提供了相应的函数来把字符串格式的IP转换成整数INET_ATON,以及把整数格式的IP转换成字符串的INET_NTOA。对于IPv6来说,使用VARBINARY同样可获得相同的好处,同时MySQL也提供了相应的转换函数,即INET6_ATON和INET6_NTOA。优化关联查询
如果确实需要使用关联查询的情况下,需要特别注意的是:
● 确保 ON 和 USING 字句中的列上有索引。在创建索引的时候就要考虑到关联的顺序。
当表 A 和表 B 用列 c 关联的时候,如果优化器关联的顺序是 A、B,那么就不需要在 A 表的对应列上创建索引。没有用到的索引会带来额外的负担,一般来说,除非有其他理由,只需要在关联顺序中的第二张表的相应列上创建索引。
● 确保任何的 GROUP BY 和 ORDER BY 中的表达式只涉及到一个表中的列,这样 MySQL 才有可能使用索引来优化。
索引
见:https://blog.csdn.net/zxm1306192988/article/details/80173463
事务
增删改才能用事务
事务的基本要素(ACID)
1、原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。
2、一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
3、隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
4、持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
事务的并发问题
1、更新丢失:当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题,最后的更新覆盖了由其他事务所做的更新。
2、脏读:事务A读取了事务B更新的数据(B未提交),然后B回滚操作,那么A读取到的数据是脏数据。不符合一致性。
3、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。不符合隔离性(要锁行)
4、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。不符合隔离性(要锁表)
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
MySQL事务隔离级别
● 读未提交:也叫读脏,事务可以读取其他事务未提交的数据。
● 读已提交:一个事务提交之后所做的修改对其他事务才可见。(SQL server默认的隔离级别)
● 可重复读:保证同一个事务多次相同的查询的结果是一致的。(mysql默认的隔离级别)
● 串行化读:保证读取的范围内没有新的数据插入或删除。
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大,鱼和熊掌不可兼得啊。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed,它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。
mysql默认的事务隔离级别为repeatable-read
更改隔离级别
查看当前是否关闭自动提交:
show variables like 'autocommit';
使用显式事务:
①开启事务
set autocommit=0;
start transaction;#可以省略
②编写一组逻辑sql语句
注意:sql语句支持的是insert、update、delete
设置回滚点:
savepoint 回滚点名;
③结束事务
提交:commit;
回滚:rollback;
回滚到指定的地方:rollback to 回滚点名;
savepoint的使用:
存储过程
SQL语句需要先编译然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储过程带有参数)来调用执行它。
存储过程是可编程的函数,在数据库中创建并保存,可以由SQL语句和控制结构组成。
存储过程的优点:
(1).增强SQL语言的功能和灵活性:存储过程可以用控制语句编写,有很强的灵活性,可以完成复杂的判断和较复杂的运算。
(2).标准组件式编程:存储过程被创建后,可以在程序中被多次调用,而不必重新编写该存储过程的SQL语句。而且数据库专业人员可以随时对存储过程进行修改,对应用程序源代码毫无影响。
(3).较快的执行速度:如果某一操作包含大量的Transaction-SQL代码或分别被多次执行,那么存储过程要比批处理的执行速度快很多。因为存储过程是预编译的。在首次运行一个存储过程时查询,优化器对其进行分析优化,并且给出最终被存储在系统表中的执行计划。而批处理的Transaction-SQL语句在每次运行时都要进行编译和优化,速度相对要慢一些。
(4).减少网络流量:针对同一个数据库对象的操作(如查询、修改),如果这一操作所涉及的Transaction-SQL语句被组织进存储过程,那么当在客户计算机上调用该存储过程时,网络中传送的只是该调用语句,从而大大减少网络流量并降低了网络负载。
(5).作为一种安全机制来充分利用:通过对执行某一存储过程的权限进行限制,能够实现对相应的数据的访问权限的限制,避免了非授权用户对数据的访问,保证了数据的安全。
一、创建存储过程:
create procedure 存储过程名(参数模式 参数名 参数类型)
begin
存储过程体
end
注意:
1.参数模式:in、out、inout,其中in可以省略
2.存储过程体的每一条sql语句都需要用分号结尾
例:
#查询 t_student 表的行数,赋给变量s,并返回
DELIMITER //
CREATE PROCEDURE myproc(OUT s int)
BEGIN
SELECT COUNT(*) INTO s FROM t_student;
END
//DELIMITER ;
#调用
SET @s=1;#用户变量一般以@开头
CALL myproc(@s);
SELECT @s;
#计算a和b的值,并输出
DELIMITER //
CREATE PROCEDURE pr_add(a int,b int)
BEGIN
DECLARE c int;
IF
a is NULL THEN SET a=0;
END IF;
IF
b is NULL THEN SET b=0;
END IF;
SET c=a+b;
SELECT c AS 和;
END
//DELIMITER ;
#调用
CALL pr_add(10,20)
参数:
● IN参数的值必须在调用存储过程时指定,在存储过程中修改该参数的值不能被返回,默认为IN
● OUT:该值可在存储过程内部被改变,并可返回
● INOUT:调用时指定,并且可被改变和返回
分隔符 DELIMITER // 表示,在这之后的语句,以分号结束,解释器不会有什么反应,只有遇到了//,才认为是语句结束。注意,使用完之后,我们还应该记得把它给修改回来。
二、调用
call 存储过程名(实参列表)
举例:
调用in模式的参数:
call sp1(‘值’);
调用out模式的参数:
set @name;
call sp1(@name);
select @name;
调用inout模式的参数:
set @name=值;
call sp1(@name);
select @name;
三、查看
show create procedure 存储过程名;
四、删除
drop procedure 存储过程名;
存储过程与自定义函数的区别:
存储过程实现的过程要复杂一些,而函数的针对性较强;
存储过程可以有多个返回值,而自定义函数只有一个返回值;
存储过程一般独立的来执行,而函数往往是作为其他SQL语句的一部分来使用;
自定义函数
一、创建
create function 函数名(参数名 参数类型) returns 返回类型
begin
函数体
end
注意:函数体中肯定需要有return语句
二、调用
select 函数名(实参列表);
三、查看
show create function 函数名;
四、删除
drop function 函数名;
如:
# 随机产生一个100-109的整数
DELIMITER $$
CREATE FUNCTION rand_num() RETURNS INT(5)
BEGIN
DECLARE i INT DEFAULT 0;
SET i=FLOOR(100+RAND()*10);
RETURN i;
END
$$ DELIMITER ;
#随机产生一个长度为 n 的字符串
DELIMITER $$
CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)
BEGIN
DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ';
DECLARE return_str VARCHAR(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i<n DO
SET return_str=CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
SET i=i+1;
END WHILE;
RETURN return_str;
END
$$ DELIMITER ;
调用函数:
SELECT rand_num();
SELECT rand_string(4);
MySQL RAND()函数调用可以在0和1之间产生一个随机数
MySQL substring(str, pos, len) 函数,从第pos个位置开始截取(包含此位置的字符),截取len 个字符
视图
含义:虚拟表,和普通表一样使用
mysql 5.1 版本出现的新特性
通过视图查询中定义的表动态生成的虚拟表,只保存SQL逻辑,不保存数据。
应用场景:
● 多个地方用到同样的查询结果
● 该查询结果使用的SQL语句较复杂
好处:
● 重用SQL语句
● 简化复杂的SQL操作,不必知道它查询的细节
● 保护数据,提高安全性
案例:查询特定姓氏的学生名和专业名:
SELECT stuname,majorname
FROM stuinfo s
INNER JOIN major m ON s.majorid=m.id
WHERE s.stuname like '张%';
用视图的形式现实:
可以将连接查询定义为视图,相当于一个虚拟表:
CREATE VIEW v1
AS
SELECT stuname,majorname
FROM stuinfo s
INNER JOIN major m ON s.majorid=m.id;
每次查询只要传递过滤条件:
select * from v1 WHERE s.stuname like '张%';
触发器
触发器是一种与表操作有关的数据库对象,当触发器所在表上出现指定事件时,将调用该对象,即表的操作事件触发表上的触发器的执行。
在MySQL中,创建触发器语法如下:
CREATE TRIGGER trigger_name
trigger_time
trigger_event ON tbl_name
FOR EACH ROW
trigger_stmt
其中:
trigger_name:标识触发器名称,用户自行指定;
trigger_time:标识触发时机,取值为 BEFORE 或 AFTER;
trigger_event:标识触发事件,取值为 INSERT、UPDATE 或 DELETE;
tbl_name:标识建立触发器的表名,即在哪张表上建立触发器;
trigger_stmt:触发器程序体,可以是一句SQL语句,或者用 BEGIN 和 END 包含的多条语句。
由此可见,可以建立6种触发器,即:BEFORE INSERT、BEFORE UPDATE、BEFORE DELETE、AFTER INSERT、AFTER UPDATE、AFTER DELETE。
另外有一个限制是不能同时在一个表上建立2个相同类型的触发器,因此在一个表上最多建立6个触发器。
假设系统中有两个表:
班级表 class(班级号 classID, 班内学生数 stuCount)
学生表 student(学号 stuID, 所属班级号 classID)
要创建触发器来使班级表中的班内学生数随着学生的添加自动更新,代码如下:
DELIMITER $
create trigger tri_stuInsert after insert
on student for each row
begin
declare c int;
set c = (select stuCount from class where classID=new.classID);
update class set stuCount = c + 1 where classID = new.classID;
end $
DELIMITER ;
用到BEFORE INSERT 如每插入一个学生前,都在其学号前加“2013”
查看触发器:
SHOW TRIGGERS;
删除触发器:
DROP TRIGGER IF EXISTS `tri_stuInsert`;
参考:https://www.cnblogs.com/CraryPrimitiveMan/p/4206942.html
事件
mysql5.1版本开始引进event概念。event既“时间触发器”,与triggers的事件触发不同,event类似与linux crontab计划任务,用于时间触发。通过单独或调用存储过程使用,在某一特定的时间点,触发相关的SQL语句或存储过程。
适用范围:
对于每隔一段时间就有固定需求的操作,如创建表,删除数据等操作,可以使用event来处理。
例如:使用event在每月的1日凌晨1点自动创建下个月需要使用的三张表。
每天清除数据表中的过期的记录。
查看EVENT:
SHOW EVENTS;
开启定时器:
SHOW VARIABLES LIKE 'event_scheduler';
SET GLOBAL event_scheduler = 1;
创建事件:
CREATE
[DEFINER = { user | CURRENT_USER }]
EVENT
[IF NOT EXISTS]
event_name
ON SCHEDULE schedule
[ON COMPLETION [NOT] PRESERVE]
[ENABLE | DISABLE | DISABLE ON SLAVE]
[COMMENT 'comment']
DO event_body;
具体参考:https://blog.csdn.net/lixia755324/article/details/53923856
分页查询
当要显示的数据,一页显示不全,需要分页提交SQL请求
语法:
特点:
①limit 语句放在查询语句的最后
②分页查询SQL公式:
select 查询列表
from 表
limit (page-1)*size,size;
SQL注入
SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令,从而利用系统的 SQL 引擎完成恶意行为的做法。
如: select * from user where username=‘admin’ or ‘a’=’a’ ,SQL语句恒成立,参数admin无意义。
防止SQL注入的方式:
- 预编译语句 PreparedStatement :如 select * from user where username=? ,预编译的SQL语句不是有具体数值的语句,而是用(?)来代替具体数据,然后在执行的时候再调用setXX(问号下标,数值)方法把具体的数据传入。使用此种方式,SQL语句已经预编译好,参数中含有的破坏性的指令不会被数据库所运行。
- Mybatis框架中的mapper 方式中的 # 也能很大程度的防止 SQL 注入($无法防止SQL注入)
主从复制、读写分离、负载均衡
主从复制(replication):解决容灾问题,容灾需要保证数据库切换的实时性和数据的一致性。
读写分离:是一种业务类应用解决读流量单机无法承受的方式,学名叫 scale out (横向扩展),读写分离类的业务是架设在主从复制基础上的。
可以实现->负载均衡
三者关系:
这幅图的load balance做在了业务层,而读写的路由逻辑由业务层在控制。
这幅图,由一个通用中间层解决了读写分离问题,顺便也做了数据库的负载均衡。
读写分离式数据库负载均衡的一种解决方案。
分库分表
单表行数超过 500 万行或者单表容量超过 2 GB ,才推荐进行分库分表。
说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
系统层面的“服务化” 拆分操作,能够解决业务系统层面的耦合和性能瓶颈,有利于系统的扩展维护。
而数据库层面的拆分,道理也是相通的。与服务的“治理”和“降级”机制类似,我们也能对不同业务类型的数据进行“分级”管理、维护、监控、扩展等。
垂直分库:
按照业务模块来划分出不同的数据库,而不是将所有数据表都放在一个数据库中。
数据库的连接资源比较宝贵且单机处理能力也有限,在高并发场景下,垂直分库一定程度上能够突破IO、连接数及单机硬件资源的瓶颈,是大型分布式系统中优化数据库架构的重要手段。
垂直分表:
“大表拆小表”,拆分是基于关系型数据库中的“列”(字段)进行的。通常情况,某个表中的字段比较多,可以新建立一张“扩展表”,将不经常使用或者长度较大的字段拆分出去放到“扩展表”中。
水平分表 :
水平分表也称为横向分表,比较容易理解,就是将表中不同的数据行按照一定规律分布到不同的数据库表中(这些表保存在同一个数据库中),这样来降低单表数据量,优化查询性能。最常见的方式就是通过主键或者时间等字段进行Hash和取模后拆分。
我们在提供检索时,应该根据业务的需求,找到分表的标准,并在检索页面约束用户检索的方式,并结合分页,来减小数据库的压力。
水平分表,能够降低单表的数据量,一定程度上可以缓解查询性能瓶颈。但本质上这些表还保存在同一个库中,所以库级别还是会有IO瓶颈。所以,一般不建议采用这种做法。
水平分库分表:
和水平分表的思想相同,唯一不同的就是将这些拆分出来的表保存在不同的数据中。
某种意义上来讲,有些系统中使用的“冷热数据分离”(将一些使用较少的历史数据迁移到其他的数据库中。而在业务功能上,通常默认只提供热点数据的查询),也是类似的实践。
在高并发和海量数据的场景下,分库分表能够有效缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源的瓶颈。当然,投入的硬件成本也会更高。同时,这也会带来一些复杂的技术问题和挑战(例如:跨分片的复杂查询,跨分片事务等)