一、数据库设计三大范式
第一范式:原子性,要求数据库的每一列都不可再分
比如数据库中有一列为“地址”,那么地址中必须是详细的地址,而不能是单独的省份、城市,这样就不满足原子性,需要将地址再分割成省份、城市等。
第二范式:唯一性,数据库每一列都要和主键相关
比如有一张订单表,联合主键为订单编号和商品编号,每一个订单中都有不同的商品,那么后面的列就不能存在商品名称、商品信息这些与订单编号无关的列,应该新建一张表单独存在商品数据。
第三范式:独立性,不能存在非主键列依赖于另外一个非主键列
比如有一张学生表,字段为学号、学生、所在院校、院校电话,那么存在传递关系:(学号) → (学生)→(所在学院) → (学院电话),存在冗余。
二、sql语句
2.1 drop、delete 与 truncate 区别?
- drop:drop table 表名,直接将表删除
- truncate: truncate table 表名 ,只删除表中数据,保留表结构
- delete:delete from 表名 where 列名=值,表示删除某一行的数据。
2.2 sql执行顺序
- from语句
- on语句
- join添加外部行
- where
- group by 分组
- 聚合函数(AVG、COUNT、SUM等)
- having 筛选
- select 选出指定列
- order by排序
- limit 返回指定行
2.3 having与where的区别
having必须要与group by连用,where 用于过滤指定的行,后面不能加聚合函数
2.4 join连接表
使用join连接两个表的基本语法如下:
SELECT table1.column1, table2.column2...
FROM table1
JOIN table2
ON table1.common_column1 = table2.common_column2
on和where的区别:
on是连接表的连接条件,where是临时表生成以后,再对表中的数据进行过滤。
join连接方式有以下几种:
连接类型 | 说明 |
inner join 内连接 | 默认连接方式,当两个表都满足条件时才会返回行 |
left join/left out join 左(外)连接 | 返回左表中所有的行+右表对应的数据 |
right join/right out join 右(外)连接 | 返回右表中所有的行+左表对应的数据 |
full join 全连接 | 只要其中有一个表存在满足条件的记录,就返回行 |
三、数据库索引
3.1 索引的概念
索引是一种特殊的文件,包含着对数据表中所有记录的引用指针
通俗点说,索引就好比是一本书的目录,能加快数据库的查询速度
例如需要遍历 200 条数据,在没有索引的情况下,数据库会遍历全部 200 条数据后选择符合条件的
而有了相应的索引后,数据库会直接在索引中查找符合条件的选项
数据库索引就是为了提高表的搜索效率而对某些字段中的值建立的目录
3.2 索引的优缺点
优点:
- 使用索引可以大大加快 数据的检索速度(大大减少检索的数据量), 这也是创建索引的最主要的原因。
- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
缺点:
- 创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。
- 索引需要使用物理文件存储,也会耗费一定空间。
3.3 索引的类型
按照存储方式划分:
- 聚簇索引(聚集索引):索引结构和数据一起存放的索引,InnoDB 中的主键索引就属于聚簇索引。
- 非聚簇索引(非聚集索引):索引结构和数据分开存放的索引,二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。
按照应用维度划分:
- 主键索引:加速查询 + 列值唯一(不可以有 NULL)+ 表中只有一个。
- 普通索引:仅加速查询。
- 唯一索引:加速查询 + 列值唯一(可以有 NULL)。
- 覆盖索引:一个索引包含(或者说覆盖)所有需要查询的字段的值。
- 联合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并。
- 全文索引:对文本的内容进行分词,进行搜索。目前只有
CHAR
、VARCHAR
,TEXT
列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
聚簇索引介绍
聚簇索引(Clustered Index)即索引结构和数据一起存放的索引,并不是一种单独的索引类型。InnoDB 中的主键索引就属于聚簇索引。
在 MySQL 中,InnoDB 引擎的表的 .ibd
文件就包含了该表的索引和数据,对于 InnoDB 引擎表来说,该表的索引(B+树)的每个非叶子节点存储索引,叶子节点存储索引和索引对应的数据。
聚簇索引的优缺点
优点:
- 查询速度非常快:聚簇索引的查询速度非常的快,因为整个 B+树本身就是一颗多叉平衡树,叶子节点也都是有序的,定位到索引的节点,就相当于定位到了数据。相比于非聚簇索引, 聚簇索引少了一次读取数据的 IO 操作。
- 对排序查找和范围查找优化:聚簇索引对于主键的排序查找和范围查找速度非常快。
缺点:
- 依赖于有序的数据:因为 B+树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或 UUID 这种又长又难比较的数据,插入或查找的速度肯定比较慢。
- 更新代价大:如果对索引列的数据被修改时,那么对应的索引也将会被修改,而且聚簇索引的叶子节点还存放着数据,修改代价肯定是较大的,所以对于主键索引来说,主键一般都是不可被修改的。
非聚簇索引介绍
非聚簇索引(Non-Clustered Index)即索引结构和数据分开存放的索引,并不是一种单独的索引类型。二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。
非聚簇索引的叶子节点并不一定存放数据的指针,因为二级索引的叶子节点就存放的是主键,根据主键再回表查数据。
非聚簇索引的优缺点
优点:
更新代价比聚簇索引要小 。非聚簇索引的更新代价就没有聚簇索引那么大了,非聚簇索引的叶子节点是不存放数据的
缺点:
- 依赖于有序的数据:跟聚簇索引一样,非聚簇索引也依赖于有序的数据
- 可能会二次查询(回表):这应该是非聚簇索引最大的缺点了。 当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。
四、sql优化
(1)查询sql尽量不要使用select *。而是具体字段
- 字段多时,大表能达到100多个字段甚至达200多个字段
- 只取需要的字段,节省资源、减少网络开销
- select * 进行查询时,很可能不会用到索引,就会造成全表扫描
(2)高效分页
select id,name,age from user limit 10000, 20;
mysql会查询10020条,然后丢弃前面10000条,这个比较浪费资源
可以优化:
select id,name,age from user id>10000 limit 20;
(3)减少不必要的排序
如果我们对结果没有排序的要求,就尽量少用排序;
如果排序字段没有用到索引,也尽量少用排序;
另外,分组统计查询时可以禁止其默认排序
SELECT goods_id,count(*) FROM t GROUP BY goods_id;
默认情况下,Mysql会对所有的GROUP BT col1,col2…
的字段进行排序,也就是说上述会对 goods_id进行排序,如果想要避免排序结果的消耗,可以指定ORDER BY NULL
禁止排序:
SELECT goods_id,count(*) FROM t GROUP BY goods_id ORDER BY NULL
(4)将多次插入换成批量插入
INSERT INTO t(id, name) VALUES(1, 'aaa');
INSERT INTO t(id, name) VALUES(2, 'bbb');
INSERT INTO t(id, name) VALUES(3, 'ccc');
—>
INSERT INTO t(id, name) VALUES(1, 'aaa'),(2, 'bbb'),(3, 'ccc');
(5)优化Group By 语句
如果对group by
语句的结果没有排序要求,要在语句后面加 order by null
(group 默认会排序);
尽量让group by
过程用上表的索引,确认方法是explain结果里没有Using temporary
和 Using filesort
;
如果group by
需要统计的数据量不大,尽量只使用内存临时表;也可以通过适当调大tmp_table_size
参数,来避免用到磁盘临时表;
- 如果数据量实在太大,使用
SQL_BIG_RESULT
这个提示,来告诉优化器直接使用排序算法(直接用磁盘临时表)得到group by
的结果。
使用where子句替换Having子句:避免使用having子句,having只会在检索出所有记录之后才会对结果集进行过滤,这个处理需要排序分组,如果能通过where子句提前过滤查询的数目,就可以减少这方面的开销。
(6) 避免索引失效
1.最佳左前缀法则
如果索引了多列,要遵守最左前缀法则,指的是查询从索引的最左前列开始并且不跳过索引中的列。Mysql查询优化器会对查询的字段进行改进,判断查询的字段以哪种形式组合能使得查询更快,所有比如创建的是(a,b)索引,查询的是(b,a),查询优化器会修改成(a,b)后使用索引查询。
2.不在索引列上做任何操作
1.计算:对索引进行表达式计算会导致索引失效,如 where id + 1 = 10
,可以转换成 where id = 10 -1
,这样就可以走索引
2.函数:select * from t_user where length(name)=6;
此语句对字段使用到了函数,会导致索引失效
从 MySQL 8.0 开始,索引特性增加了函数索引,即可以针对函数计算后的值建立一个索引,也就是说该索引的值是函数计算后的值,所以就可以通过扫描索引来查询数据。
alter table t_user add key idx_name_length ((length(name)));
(自动/手动)类型转换
- (字符串类型必须带''引号才能使索引生效)字段是varchar,用整型进行查询时,无法走索引,如
select * from user where phone = 13030303030
;
Mysql 在执行上述语句时,会把字段转换为数字再进行比较,所以上面那条语句就相当于:select * from user where CAST(phone AS signed int) = 13030303030
; CAST 函数是作用在了 phone 字段,而 phone 字段是索引,也就是对索引使用了函数!所以索引失效
- 字段是int,用string进行查询时,mysql会自动转化,可以走索引,如:
select * from user where id = '1'
;
MySQL 在遇到字符串和数字比较的时候,会自动把字符串转为数字,然后再进行比较。以上这条语句相当于:select * from user where id = CAST(“1” AS signed int)
,索引字段并没有用任何函数,CAST 函数是用在了输入参数,因此是可以走索引扫描的。
3.存储引擎不能使用索引中范围条件右边的列。
如这样的sql: select * from user where username='123' and age>20 and phone='1390012345'
,其中username, age, phone都有索引,只有username和age会生效,phone的索引没有用到。
4.尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致))
如select age from user
,减少select *
5.mysql在使用负向查询条件(!=、<>、not in、not exists、not like)的时候无法使用索引会导致全表扫描。
对于一棵B+树,根节点是40,如果你的条件是等于20,就去左面查,你的条件等于50,就去右面查,但是你的条件是不等于66,索引应该咋办?还不是遍历一遍才知道。
6.is null, is not null
也无法使用索引,在实际中尽量不要使用null(避免在 where 子句中对字段进行 null 值判断) 不过在mysql的高版本已经做了优化,允许使用索引
对于null的判断会导致引擎放弃使用索引而进行全表扫描。
7.like 以通配符开头(%abc..
)时,mysql索引失效会变成全表扫描的操作。
所以最好用右边like ‘abc%’。如果两边都要用,可以用select username from user where username like '%abc%'
,其中username是必须是索引列,才可让索引生效
假如index(a,b,c), where a=3 and b like ‘abc%’ and c=4
,a能用,b能用,c不能用,类似于不能使用范围条件右边的列的索引
对于一棵B+树索引来讲,如果根节点是字符def,假如查询条件的通配符在后面,例如abc%,则其知道应该搜索左子树,假如传入为efg%,则应该搜索右子树,如果通配符在前面%abc,则数据库不知道应该走哪一面,就都扫描一遍了。
8.少用or,在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效。
select * from t_user where id = 1 or age = 18;
-- id有索引,name没有,此时没法走索引
因为 OR 的含义就是两个只要满足一个即可,因此只有一个条件列是索引列是没有意义的,只要有条件列不是索引列,就会进行全表扫描。
9.在组合/联合索引中,将有区分度的索引放在前面
如果没有区分度,例如用性别,相当于把整个大表分成两部分,查找数据还是需要遍历半个表才能找到,使得索引失去了意义。
10.使用前缀索引
短索引不仅可以提高查询性能而且可以节省磁盘空间和I/O操作,减少索引文件的维护开销,但缺点是不能用于 ORDER BY 和 GROUP BY 操作,也不能用于覆盖索引。
比如有一个varchar(255)
的列,如果该列在前10个或20个字符内,可以做到既使前缀索引的区分度接近全列索引,那么就不要对整个列进行索引。为了减少key_len
,可以考虑创建前缀索引,即指定一个前缀长度,可以使用count(distinct leftIndex(列名, 索引长度))/count(*)
来计算前缀索引的区分度。
五、事务隔离级别
SQL 标准定义了四个隔离级别:
- READ-UNCOMMITTED(读取未提交) :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
- READ-COMMITTED(读取已提交) :允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
- REPEATABLE-READ(可重复读) :对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- SERIALIZABLE(可串行化) :最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。