mysql

256 阅读18分钟

数据库三大范式

学生信息表

学号姓名班级学生宿舍成绩课程编号课程名称
1小明1广东省深圳市福田区223语文
2小明1广东省深圳市福田区671英语
3小红2广东省深圳市福田区992数学

第一范式

每一个字段都具备原子性,不可再区分。
例子中地址几不符合原子性,应该将地址在划分

第二范式

非码属性需要完全依赖候选码

分析依赖关系

推出候选码为学号,课程编号。

依赖关系
学号,课程编号->姓名,班级,学生宿舍,课程名称,成绩
课程编号->课程名
学号->姓名,班级,学生宿舍 班级->学生宿舍

由此可知非主属性并不完全依赖于候选码。此时应该将课程编号与学号分为两张表。
学生信息表

| 学号 | 姓名 | 班级 | 学生宿舍 | | -- | -- | -- | -- | -- | | 1 |小明 |1 | 广东省深圳市福田区 | | 2 |小明 |1 | 广东省深圳市福田区 | | 3 |小红 |2 | 广东省深圳市福田区 |

成绩表

学号成绩课程编号
1223
2671
3992

课程表

| 课程编号 | 课程名称 | | -- | -- | -- | | 1 | 语文 | | 2 | 英语 | | 3 | 数学 |

第三范式

在第二范式的基础上,消除传递依赖。

举个例子

| 学号 | 姓名 | 班级 | 学生宿舍楼 | | -- | -- | -- | -- | -- | | 1 |小明 |1 | 广东省深圳市福田区 | | 2 |小明 |1 | 广东省深圳市福田区 | | 3 |小红 |2 | 广东省深圳市福田区 |

依赖关系 学号->姓名,(班级)->学生宿舍楼
此时存在传递依赖,需要消除

学生表

| 学号 | 姓名 | 班级 | | -- | -- | -- | -- | | 1 |小明 |1 | | 2 |小明 |1 | | 3 |小红 |2 |

班级表

| 班级 | 学生宿舍楼 | | -- | -- | -- | |1 | 广东省深圳市福田区 | |1 | 广东省深圳市福田区 | |2 | 广东省深圳市福田区 |

触发器(trigger)

什么是触发器?

触发器是与表有关的数据库对象,当表满足定义的条件,则执行触发器中定义的语句集合

触发器的特性

  1. begin、end体
  2. 什么条件触发
  3. 什么时候触发
  4. 触发频率
  5. 触发器定义在表上

尽量少使用触发器

注意begin-end期间的语句执行效率一定要高,触发器针对的是每一行,对增删改频率高的表上最好不要使用触发器

创建触发器

CREATE
    [DEFINER = { user | CURRENT_USER }]
TRIGGER trigger_name
trigger_time trigger_event
ON tbl_name FOR EACH ROW
  [trigger_order]
trigger_body

trigger_time: { BEFORE | AFTER }

trigger_event: { INSERT | UPDATE | DELETE }

trigger_order: { FOLLOWS | PRECEDES } other_trigger_name

BEFORE和AFTER参数指定了触发执行的时间,在事件之前或是之后。

FOR EACH ROW表示任何一条记录上的操作满足触发事件都会触发该触发器,也就是说触发器的触发频率是针对每一行数据触发一次。

tigger_event详解:

  ①INSERT型触发器:插入某一行时激活触发器,可能通过INSERT、LOAD DATA、REPLACE 语句触发(LOAD DAT语句用于将一个文件装入到一个数据表中,相当与一系列的INSERT操作);

  ②UPDATE型触发器:更改某一行时激活触发器,可能通过UPDATE语句触发;

  ③DELETE型触发器:删除某一行时激活触发器,可能通过DELETE、REPLACE语句触发。

trigger_order是MySQL5.7之后的一个功能,用于定义多个触发器,使用follows(尾随)或precedes(在…之先)来选择触发器执行的先后顺序。

创建只有一个执行语句的触发器

CREATE TRIGGER trig AFTER INSERT
ON work FOR EACH ROW
INSERT INTO name VALUES('cctv');

创建由多个执行语句的触发器

CREATE TRIGGER trig2 BEFORE DELETE
ON work FOR EACH ROW
BEGIN
INSERT INTO name VALUES('cctv');
INSERT INTO sex VALUES('boy');
END

NEW与OLD

Mysql定义NEW和OLD,来表示触发器中发生变化的内容

  1. 在INSERT中,NEW表示将要或者已经插入的数据
  2. 在UPDATE中,OLD表示将要或者已经被修改的原数据,NEW表示将要或者已经修改的新数据
  3. 在DELETE中,OLD表示将要或者已经被删除的原数据

注意OLD为只读,而NEW可以在触发器中使用SET赋值

示例

IF NEW.amount < 0 THEN
SET NEW.amount = 0;
ELSEIF NEW.amount > 100 THEN
SET NEW.amount = 100;
END IF;

查看触发器

SHOW TRIGGERS 

显示所有触发器信息

select * from school.triggers 
where trigger_name='upd_check';

显示某表中某个触发器的信息

删除触发器

DROP TRIGGER school.triggerName

mysql的逻辑架构

总体概述

客户端访问mysql服务

  1. 首先访问连接池
  2. Caches&Buffers缓存、缓冲 访问缓存,如果查找到需要的信息便可提高查询效率 缓存:读信息 缓冲:写信息
  • 想要命中缓存,必须sql完全相同,因为使用的是键值对存储的方式,关键字为sql语句
  1. SQL Interface接口
  2. Parser解析器
  3. Optimizer优化器
    不改变结果的情况下,调整sql语句的顺序 决定执行sql的执行计划
  4. Pluggable Storage Engines 存储引擎 真正去处理数据
  5. 写入部分内容在缓存中
  6. 返回结果给客户端

利用show profile命令查看sql的执行周期

1. mac下配置my.cnf文件

在文件夹/etc中创建my.cnf文件,注意创建的过程中可能会因为权限不够无法创建,使用sudo即可

2. 配置参数

在my.cnf文件中配置以下参数

  • query_cache_type=1;
  • query_cache_size=6000000;

3. 进入mysql中

  1. 输入set profiling=1
  2. show profiles即可
    也可更加详细 show profile cpu,block io for query (查询编号),此时可以看到执行过程

mysql存储引擎

SHOW ENGINES命令

查看当前mysql提供的存储引擎

各个引擎的介绍

Archive引擎

  • 只支持INSERT、SELECT操作,在Mysql5.1之前不支持索引。
  • 适合日志和数据采集类应用
  • 比MyISAM和InnoDB表要小很多

Blackhole引擎

没有实现任何存储机制,会丢弃所有插入的数据。服务器会记录Blackhole表的日志,所以可以用于复制数据到备库,或者简单地记录到日志。

CSV引擎

  • 将普通的CSV文件作为mysql的表处理,但是不支持索引。
  • 可以作为一种数据交换机制
  • 存储的数据可以直接在操作系统里使用文本编辑器或者表格读取
  • CSV文件,列与列之间使用,分隔。行与行之间使用回车分隔

Memory引擎

将数据存储在内存中,并且这些数据不会被修改,重启后丢失也没关系

Federated引擎

  • 场景:两台不同的机器,需要关联他们各自数据库中各自的某张表
  • 在一台机中使用该引擎,创建一张和另一张表名相同的表,不存储数据,存储访问另一张表的信息
  • 效率底下,只能进行简单的直接关联

最常用的存储引擎MyISAM、InnoDB

MyISAMInnoDB
外键不支持支持
事务不支持支持
行表锁表锁,即使操作一条记录也会锁住整个表,不适合高并发操作行锁,操作时只锁某一行,对其他行没有影响,适合高并发的操作
缓存只缓存索引,不缓存真实数据索引和数据都缓存
关注节省资源、消耗少并发写、事务、更大
  • 设置外键的问题
    1. 每插入一个新的数据都需要重新扫描,数据大时效率底下
    2. 初始化数据库的时候,插入表会有先后顺序,表数量多时会很麻烦 一般来说使用代码逻辑来限制表与表之间的关系

Join图

//sql实现
全连接
SELECT * FROM A
LEFT JOIN B
ON A.KEY = B.KEY
UNION
SELECT * FROM B
LEFT JOIN A
ON A.KEY = B.KEY
WHERE A.KEY IS NULL
AB表独有
SELECT * FROM A
LEFT JOIN B
ON A.KEY = B.KEY
WHERE B.KEY IS NULL
UNION
SELECT * FROM B
LEFT JOIN A
ON A.KEY = B.KEY
WHERE A.KEY IS NULL

mysql不支持全连接,使用union连接左右连接即可实现
如果想用union进行连接,需要确保字段数、字段类型 相同

UNION和UNION ALL的区别:后者不去重
如果确定上下结果不重复时,使用UNION ALL,因为去重需要时间

sql语句优化

查询变慢的原因

  • 数据过多 -- 分库分表
  • 关联太多的表 -- SQL优化
  • 没有充分利用索引 -- 建立索引 优化效果最明显
    mysql创建主键时,会自动创建索引(主键索引)
  • 服务器调优以及各个参数设置 -- 调整my.cnf

索引

何为索引

定义

帮助mysql高效获取数据的数据结构。

理解

数据库除了存储数据之外,还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用数据。 一般来说索引本身很大,不可能存储在内存中,往往以文件的形式存储在磁盘上。

优势

  • 类似图书馆简历数目索引,提高数据检索的效率,降低数据库的IO成本
  • 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗

劣势

  • 虽然索引大大提高了查询速度快,但是会降低更新表的速度,如INSERT、UPDATE、DELETE操作。Mysql不仅要保存数据,还要保存一下索引文件每次更新添加索引列的字段,调整因为更新所带来的键值变化后的索引信息
  • 实际上索引也是一张表,保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的

mysql索引结构

BTree索引

结点内容

  • 关键字
  • 指向下一个子树的指针
  • 指向记录的指针

时间复杂度

B+Tree索引

结点内容

  • 关键字
  • 指向下一个子树的指针
  • 叶子结点有指向记录的指针,所以每次查询都需要查到叶子结点
  • 叶子结点有指向下一个叶子结点的指针

聚族索引与非聚族索引

聚族索引

一种数据存储方式,"聚族"表示数据行和相邻的键值聚族的存储在一起,如上图左侧部分,数据行在磁盘中的排序与索引的排序一致

聚族索引的好处

查找一定范围的数据时,由于数据存储的位置都紧密相连,因此发生的IO次数将大大减少

聚族索引的限制

  • mysql只有innodb数据引擎支持聚族索引,而Myisam不支持
  • 因为数据物理存储排序方式只有一种,所以每个mysql表只能有一个聚族索引,一般来说就是主键索引
  • 为了充分利用聚族索引的特性,因此innodb表尽量使用有序的顺序id,而尽量不使用无序id

为何mysql选择了B+树

在内存有限的情况下,因为B+树非叶子结点占用空间小,每次IO读取的节点数更多,因此在基数大的情况下B+树的IO次数明显要比B树的IO次数少。

B树与B+树之间的区别

  • B树关键字和记录指针是存放在一起的,而B+树只有叶子结点才存放记录指针。
  • B树查找的关键字越接近根节点查找速度越快,只需要找到关键字即可查找到记录的存在。而B+树查找时间基本相同,每次都需要从根节点查找到叶子结点。从此看好似B树性能要比B+树好,但是在实际的操作中,B+树叶子结点没有存放记录指针,因此每个结点容纳的元素比B树多,发生IO的次数可以减少,虽然查找的次数变多,但是每次发生一次IO的时间就相当于多次在内存中进行比较操作的时间。
  • B+树叶子结点使用指针连接在一起,方便遍历

索引分类

基本语法

  • 查看:SHOW INDEX FROM tb
  • 创建:CREATE INDEX ON tb
  • 删除:DROP INDEX [indexName] ON tb

单值索引

一个索引包含单个列,一张表可以有多个单值索引

语法

  • 单独创建 CREATE INDEX inx_name ON tb(cl)
  • 随表创建
CREATE TABLE tb(
	XXXXXX
	PRIMARY KEY(XX)
    KEY(ZZ)
)

唯一索引

索引列的值必须唯一

语法

  • 单独创建 CREATE UNIQUE INDEX inx_name_union ON tb(cl)
  • 随表创建
CREATE TABLE tb(
	XXXXXX
	PRIMARY KEY(XX)
    UNIQUE(ZZ)
)

主键索引

设定为主键后数据库会自动创建索引,innoDB为聚族索引

语法

  • 单独创建 ALTER TABLE tb ADD PRIMARY KEY (cl)
  • 随表创建
CREATE TABLE tb(
	XXXXXX
	PRIMARY KEY(XX)
)

复合索引

一个索引包含多个列 组合索引在上一个列结果的基础上查找下一个字段对应的列,意味着组合索引命中必须要有先后顺序并且不可跳过。

语法

  • 单独创建 CREATE INDEX inx_name_union ON tb(cl1,cl2)
  • 随表创建
CREATE TABLE tb(
	XXXXXX
	PRIMARY KEY(XX)
    KEY(cl1,cl2)
)

哪些情况需要创建索引

  • 主键自动创建唯一索引
  • 频繁作为查询条件的字段应该创建索引
  • 查询中与其他表关联的字段,外键关系建立索引
  • 单键/组合索引的选择问题,组合索引性价比更高
    • mysql只会选择它认为最好的索引,即使多个字段分别都有单值索引,mysql也只会选择一个单值索引,因此组合索引的性价比更高
  • 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
    • ORDER BY的字段也应该建立索引,因为B+树叶子结点已经全部按照数据大小的顺序排序好并且连接在一起了
  • 查询中统计
    • COUNT()意味着需要遍历所选字段,B+树叶子结点通过指针全部连接在一起了,基数大的时候查询效率会更高

哪些情况不需要创建索引

  • 表记录太少(几十条几百条)
  • 经常增删改查的表或者字段
  • WHERE条件用不到的字段不创建索引
  • 过滤性不好的不适合建索引
    • 比如性别,总共就男女两种,无法区分记录

性能分析 Explain

使用Explain关键字可以查看执行计划,模拟优化器执行SQL查询语句,从而知道Mysql是如何处理sql语句的,以此来分析sql语句或者是表结构的性能瓶颈

Explain的作用

  • 查看表的读取顺序 由ID序号实现
  • 哪些索引可以使用
  • 数据读取操作的操作类型
  • 哪些索引被实际使用
  • 表之间的引用
  • 每张表有多少行被物理查询

如何使用Explain

  • Explain+sql语句
  • 执行计划包含的信息
  • 建表脚本

字段解释

id

select查询的序号,包含一组数字,表示查询中执行select子句或者操作表的顺序

  • 三种情况
    • id相同,执行顺序由上至下
    • id不同,如果是子查询,id的序号会递增,id值越大优先级越高
    • id有相同的也有不同的,从大到小,由上到下

作用

id每个号码代表一次独立的查询,查询数越少越好

select_type

  • SIMPLE 简单的select查询,查询中不包含子查询或者UNION
  • PRIMARY 查询中包含任何负责的子部分,最外层查询则被称为Primary
  • DERIVED 在FROM列表中包含的子查询被标记为DERIVED(衍生),MYSQL会递归执行这些子查询,把结果放在临时表里。
  • SUBQUERY 在SELECT或者WHERE列中包含了子查询
  • DEPENDENT SUBQUERY 在SELECT或者WHERE列中包含了子查查询,子查询基于外层(关键字IN)
  • UNCACHEBLE SUBQUERY @@XXX系统变量,变量的值随时会改变,所以不需要缓存
  • UNION
  • UNION RESULT UNION组成后的结果

table

显示该行数据是关于哪张表的

partitions

表示分区表中的命中情况,非分区表该项为null

type

显示查询使用了何种类型 最好->最差 system>const>eq_ref>ref>range>index>ALL

  • system 表中只有一行记录(等于系统表),这是const类型的特例,平时不会出现
  • const 表示通过索引一次找到,const用于比较primary 可以或者unique索引。因为只匹配一行数据,所以速度很快,如果将主键置于where列表中,Mysql就能将查询转换为一个常量
  • eq_ref 唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键和唯一索引扫描
  • ref 非唯一性索引扫描,返回匹配某个单独值的所有行,数据查找和扫描的混合体
  • !range 检索给定范围的行,一般出现了between、<、>、in等查询
  • !!index 出现了索引,但是没有使用将索引作为条件进行过滤,一般只是用了覆盖或者排序
  • !!!all 遍历全表来找到匹配的行
  • index_merge 查询中使用了多个索引组合,一般出现了or关键字
  • ref_of_null 对于某个字段需要关联条件也需要null值,查询优化器会选择ref_or_null连接查询
  • index_subquery 利用索引来关联子查询,不进行全表扫描
  • unique_subquery 使用唯一索引来关联子查询

possible_keys

  • 显示可能应用在这张表中的索引,一个或者多个
  • 查询涉及到的字段上若存在索引,则该索引被列出,但不一定被实际使用

key

  • 实际使用的索引,如果为空则没有使用索引
  • 查询中若使用了覆盖索引,则该索引和查询的select字段重叠
    • 覆盖索引:索引中包含了要查询的数据列,不需要去查询记录

key_len

WHERE后筛选条件命中索引的长度,命中字段越多,查询效率越高

rows

物理扫描的行数

ref

显示索引的哪一列被使用了,越少越好

filtered

存储引擎返回的数据在server层过滤后剩下多少满足查询的记录数量的比例

Extra

额外信息

  • !!!Using filesort:ORDER BY没有使用索引
  • !!!Using temporary:GROUP BY没有使用索引
  • !!!Using join buffer:两个表关联没有使用索引
  • impossible where:sql逻辑错误
  • USING index
    • 使用了覆盖索引避免访问数据行。
    • 如果同时出现了USING WHERE表明索引被用来执行索引键值查找
    • 没有出现USING WHERE表示索引知识用于读取数据
    • 利用索引进行了排序和分组
  • USING WHERE:表明WHERE过滤条件使用了索引
  • select tables optimized away:访问了优化器,在MyISAM中已经存储了所有记录的数量,不需要打开表进行计算

查询优化

  • 批量数据脚本
  • 单表使用索引以及常见索引失效
  • 关联查询优化
  • 子查询优化
  • 排序分组优化
  • 最后使用索引的手段:覆盖索引

单表索引优化

全值匹配

条件有多少,索引有多少。组合索引中,改变字段顺序,优化器会自动调整使得索引可以被使用

最左匹配原则

查询从索引的最左端开始并且不跳过任何索引中的列

不在索引列做任何操作,如计算、函数、类型转换,会导致索引失效

出现范围查询时右侧的列索引失效,在建立索引时需要把范围查找的列放在最右边

使用不等于时(!=或者<>)无法使用索引,会导致全表扫描

IS NOT NULL无法使用索引,IS NULL可以使用

LIKE以通配符开头索引会失效,会变成全表扫描

字符串不加单引号索引失效

一般性建议

  • 单键索引尽量选择针对当前查询过滤性更好的索引
  • 在选择组合索引的时候,按照过滤性好坏的顺序一次从前往后排
  • 在选择组合索引的时候,尽量选择可以能够包含当前查询中的where中更多字段的索引
  • 在选择组合索引的时候,如果某个字段出现范围查询,尽量把该字段放在索引次序的最后
  • 书写sql时尽量避免索引失效的情况

关联查询优化

无法避免驱动表的全表扫描

建议

  • 保证被驱动表的join字段已经被索引
  • LEFT JOIN时选择小表作为驱动表,大表作为被驱动表
  • INNER JOIN,mysql自己选择驱动表和被驱动表
  • 子查询尽量不要放在被驱动表,可能使用不到索引
  • 能够直接关联的尽量直接关联,不要进行子查询

子查询优化

尽量不使用NOT IN或者NOT EXISTS使用LEFT JOIN ON XXX WHERE XXX IS NULL代替

排序分组优化

没有过滤条件,索引无效

顺序错,必定排序

方向反,必定排序

索引的选择

mysql会选择效率更高的索引

如果排序列不在索引中,filesort有两种算法

双路排序

Mysql4.1之前使用的事双路排序,

单路排序

FAQ

sql_mode:sql语句检查的原则

在开发测试时,将测试环境中的数据拉下来的同时记得将测试环境中的sql_mode设置成相同

优化器只在查询结果不改变的情况下调整字段顺序是的索引可以被使用。