B+树
- 非叶子节点不存储Data 只存储索引,可以放更多的索引
- 叶子节点包含所有索引字段
- 叶子节点用指针链接,提高区间访问的性能
存储引擎
存储引擎是用来形容数据表
- **MyISAM **
- InnoDB
MYISAM
**非聚集索引 **索引和数据是分开的
- **MYD **存储表的数据
- **MYI **存储表的索引 底层就是用的B+树
MYISAM引擎使用索引查找数据原理
当我们查询一条数据时 selec * from test where testId= 2;
当我们用这条数据查询,底层先判断sql语句的常用条件有没有索引,如果有就区MYI文件按照检索的顺序,快速定位到叶子节点,一般叶子节点的data元素存储的是索引所在行的磁盘文件地址,然后根据磁盘文件地址,去到MYD文件中快速定位到这个数据,将数据取出
InnoDB
聚集索引 索引和数据要放在一起
普通索引
- **frm **表结构信息
- **idb **存储的索引和数据
注意:
- InnoDB必须要有主键,并且推荐使用整形的自增主键,如果没有手动指定主键,InnoDB引擎会自动帮你从第一个字段开始找没有重复数据的字段作为主键,如果找不到任何唯一数据的字段,那么MYsql在后台会帮你维护一列隐藏列,确保唯一性
- 表数据文件本身就是按照B+Tree组织的一个索引结构文件
- 聚集索引,叶节点包含了完整的数据记录
UUID既不是整形也不是自增
InnoDB的索引为什么要用自增和整形呢
因为B+数每个节点根据一个范围去查找数据(也就是比大小),层层缩小范围,所以自然一个递增的索引查询的效率要比UUID那种随机生成的索引查询效率要高
BTree中对键值的大小是有限制的,InnoDB是不能超过767个字节, 对于MyISAM来说是不能超过1000个字节, 整形撑死8个字节 从空间节省的角度来看,也适合用整形
- 如果使用自增 那么对B+树造成分裂的概论非常小,因为都是从左往右一次插入的,对前面左边的数据基本不会有影响
- 但是如果不使用自增,那么,不定时从中间插入,会照成大量的分裂,分裂完成还要做平衡,所以大大影响insert语句的效率
Hash
通过hash散列算法得到一个哈希散列值,通过散列值可以快速定位到对应的一个磁盘文件地址
哈希的效率比B树的效率要高,但是,它只能查找一个确切的数字的索引(等值查找),如果是范围查询,由于哈希的数据结构的限制,那么它的效率就慢的不行,必须全表扫描
B+数 的节点已经是排好序的,从左到右依次递增的一个结构,B+树的叶子节点,两两之间有一个双向指针,每个元素中的末尾都有一块区域存放相邻节点的磁盘文件地址
B+树和B树的区别
- 非叶子节点的Data元素全部都移到了叶子节点,非叶子节点全是冗余索引
- 叶子节点上增加了区间的双向指针,每个元素中存在一个相邻节点的磁盘文件地址
B+树中非叶子节点存储的索引越多,叶子节点就能存储更多的索引
- 层级更低, IO次数更少
- 每次都需要查询到叶子节点
- 查询性能稳定,叶子节点形成有序链表,范围查询方便
回表查询
先定位主键值,再定位行记录,它的性能相较扫描一遍索引树更低
- 先通过普通索引定位到主键值的id
- 再通过聚集索引定位到行记录
Mysql执行流程
mysql主要分为Server层和存储引擎层
Server层执行流程
- **连接器 **主要是负责用户登录数据库进行一个身份验证,包过校验用户密码,权限等操作,这个权限就是当前这次连接对操作数据库的操作都是基于本次读取的权限来操作的,如果当前连接没断开,即使数据库权限被管理员修改了,那么在本次连接都会生效
- 查询缓存 mysql 会先对语句进行校验,看看这个语句是否被执行过,以Key-Value的形式存放在缓存中,如果缓存命中,则直接不用执行后续步骤,将结果返回给客户端 (Mysql 8.0之后取消)
- **分析器 ** 分析主要有两部 词法分析和语法分析
- **词法分析 **主要是提取关键字 比如Sql语句是什么执行语句,以及表名,还有条件之类
- **语法分析 **主要分析当前的SQL语句是否有语法上面个的错误,是否符合语法规范
- **优化器 **SQL底层会选择一个最优的执行方案去执行
- **执行器 **当选择了执行方案之后,mysql就准备开始执行了,首先会根据连接器加载的权限校验当前用户有没有这个权限,如果没有,则返回错误信息,如果有,就会去调用引擎接口
MySql语句和表
外键约束
要想在主表中删除数据,必须先删除完从表中字段被约束的数据
- 创建从表添加外键约束
CONSTRAINT 约束名称 FOREIGN KEY(外键字段) REFERENCES 主表(主键)
- 已有表中添加外键
alter table 表名称 add CONSTRAINT 约束名称 FOREIGN KEY(外键字段) REFERENCES 主表(主键)
表关系
- 一对一: 一般可以可以直接合并成一张表
- 业务考虑; 用户一张表,账号一张表
- 性能考虑: 常用的字段一张表,不常用的一张表
- 一对多: 常见的一种结构 一般多的那方设置外键约束
- 多对多: 一般会设置一张中间关系表, 分别有其它两张表的外键引用
多表查询
内连接查询
查询表之间有关联的数据 (无关联的数据要被剔除掉)
- 显示内连接
select * from 表1 inner join 表2 on 表关联条件 where 筛选条件
- 隐式内连接
select * from 表1,表2 where 表关联条件 and 筛选条件
外连接查询
查询一张表的全部数据和另一张表的关联数据
- 左外连接
select * from 左表 left join 右表 on 表关联条件 where 赛选条件
查询左表的全部数据和右表的关联数据 - 右外连接
select * from 左表 right join 右表 on 表关联条件 where 筛选条件
查询右表的全部数据和左表的关联数据
索引
索引有两大功能 查找 排序
索引的好处
- 索引大大减少了存储引擎需要扫描的数据量
- 由于BTree的索引是按照键值的顺序来存放的,所以可以帮助我们进行排序,这样可以避免了使用磁盘临时表,减少IO消耗,同时提高了mysql处理的能力
- 索引可以将随机IO变成顺序IO 顺序IO要比随机IO效率快得多
索引的坏处
只有当索引带来查找的好处大于索引带来系统的开销才是有效的
- 索引会增加写入操作的成本 因为我们在对数据进行新增或者修改操作的时候,还要对相关的索引进行维护,所以索引越多,操作数据时间越长,InnoDB有一个解决方法是插入缓存,将多条插入操作合并成一成一次
- 过多的索引也会影响数据库的查询性能,由于MySQL的查询优化器会根据索引的信息和查询条件选择一个合适的索引,但是如果一个查询有多个索引可以实现,就增加优化器对这些索引进行分析的时间,影响查询的效率
索引的策略
- 索引列上不能使用表达式或函数
前缀索引
CREATE INDEX 索引名 ON 表明(字段名(前缀名))
前缀索引会降低索引的一个选择性
联合索引
- 经常会被使用到的列优先放到联合索引的最左边,但如果列本身的选择性很差,那么也不适合放最左边,因为这种情况下Mysql优化器可能会认为使用全表扫描都比使用选择性很差的索引性能要高
- 选择性高的列优先
- 宽度小的列优先 宽度越小意味着一个节点中存放的索引数据越多,也代表使用索引过滤的时候IO会越小,同时也加快索引的效率
创建索引 create index 索引名 on 表名(字段名1,字段名2..)
字段1是要选择性高且宽度小
删除索引 drop index 索引名 ON 表名
添加索引 ALTER TABLE 表明 ADD INDEX 索引名(字段名)
最左前缀原则
最左前缀原则就是联合索引中最开始的索引一定要有,如果没有就不走索引,跟索引的顺序无关,但一定要有第一条索引字段, 还有就是如果是范围查询,那么联合索引是会停止执行的
Btree索引
- 如果不是按照索引最左边开始查找,则无法使用索引
- 使用索引时不能跳过索引中的列,这样跳过的列的后边索引不生效
- 如果查询中有某个列的范围查询,则其右边的所有列都无法使用索引
Hash索引
InnoDB的是自适应哈希索引,索引并不是我们自己建立的,而是存储引擎根据使用情况自行建立的
在哈希索引的表中保存了每一个哈希索引所对应的表的数据行的指针,由于哈希索引本身只存储哈希码,所以哈希的数据结构是十分紧凑的,这样使得哈希查询的效率非常高
- 使用哈希索引需要进行两次查找 先根据索引找到对应的行,然后再对行的记录进行读取,不过一般被频繁访问到的数据行是缓存到内存中的,所以对性能影响不大
- 哈希索引是按照哈希码的顺序来存储的,而不是键值 所以无法进行排序操作
- 哈希索引只能进行全值匹配查找
哈希索引不适合建立在选择性很差的表中(如年龄),容易发生大量的哈希冲突
索引覆盖
就是select的数据列只用从索引中就能获取到,不必读取数据,MYSQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件 简单说就是 索引要能覆盖所要查询的列
- 可以避免对Innodb主键索引的二次查询 二级索引在叶子节点中保存的是行的主键值,在通常情况下如果是利用二级索引来查询数据的话,在查找到相应的键值后,还要通过主键来进行二次查找才能获取我们所需要行的数据
- 可以避免MyISAM表进行系统调用,通过索引获取全部表所需要的数据,就会防止对系统调用的产生,覆盖索引对优化MyISAM存储引擎来说会更为的有效
注意:
- 如果要使用覆盖索引,一定要注意列表中只取出需要的列,不可使用select *
因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降
MYSQL优化
mysql优化的过程
- 观察,至少跑一天,看看生产的慢SQL情况
- 开启慢查询日志,设置阈值,比如超过5s的就是慢SQL,并将它抓取出来
- explain + 慢SQL分析
- 如果还没有解决, 那么进一步 show profile 查询SQL在mysql服务器里执行的细节和生命周期情况
- SQL数据库服务的参数调优
开启慢查询
Mysql数据库默认没有开启慢查询 需要手动设置这个参数,如果不是调优,不建议设置,因为开启慢查询会将日志写入文件,或多或少会带来性能影响
- 默认
SHOW VARIABLES LIKE '%slow_query_log%'
- 开启
set global slow_query_log=1;
只对当前数据库生效,如果mysql重启后将会失效 - 默认慢查询的参数是10秒
show varibales like 'long_query_time%' ;
查看慢sql执行时间 - 修改默认执行时间:
set global long_query_time=3
将十秒改为三秒 - 查询慢sql
show global status like '%Slow_queryies%;
**日志分析工具 mysqldumpslow **
在生产环境中如果要手工分析日志,查找,分析SQL,显然是个体力活,Mysql提供了日志分析工具
``
explain
ID
表的读取和加载顺序
- id 如果id相同,那么按照顺序执行,但是如果id不同,谁的值大,谁先执行,比如子查询的优先级就高于一般查询
select_type
数据读取的操作类型
- **SIMPLE **表示简单的select查询,查询中不包含子查询或者UNION
- **PTIMARY **查询中包含任何复杂的字部份,最外层查询则被标记为PRIMARY,也就是最后执行的
- **SUBQUERY **在SELECT或WHERE列表中包含了子查询
- **DERIVED **衍生 在FROM列表中包含的子查询被标记为DERIVED(衍生) MYSQL会递归执行这些子查询,把结果放在临时的表里
- **UNION **若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中,外层SELECGT将被标记为 DERIVED
- **UNION RESULT **从UNION表获取结果的SELECT
table
表示是属于哪张表的
type
顺序从最好到最差依次排列
- **system **表只有一行记录,这是const类型的特例,平时不会出现
- **const **表示通过索引一次就能够找到,const用于比较primary key或者unique索引,因为只匹配一行数据,所以很快 如将主键置于where 列表中,MYSQL就能够将查询转换成一个常量
- **eq_ref **唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描
- **ref ** 非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体
- **range **只检索给定范围的行,使用一个索引来选择行,key列表显示是使用了那个索引,一般就是在你对where语句中出现了between,<,>,in等的查询 这种范围扫描索引比全表扫描要好,因为它只需要开始于索引的某个点,而结束语另一点,不用扫描全部索引
- **index **index与All的区别是 inde类型只遍历索引树,这通常是比ALL快的,因为索引文件通常比数据文件小(也就是说虽然All和Index都是读全表,但index是从索引中读取的,而all是从硬盘中读取的)
- **ALL **表明检索全表扫描
注意: 一般情况下,至少要保证数据达到Range级别,最好能达到ref级别
possible_keys
显示可能应用到这张表的索引,一个或多个,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用
key
- 实际被查询使用的索引,如果为null,则没有使用索引
- 查询中若使用了覆盖索引,则该索引仅出现在key列表中
key_len
- 表示索引中使用的字节数,可通过该列计算查询中使用的索引长度, 在不损失精确性的情况下,长度越短越好
- key_len 显示的值为索引字段的最大可能长度,并非实际长度,及key_len是根据表定义计算而得,不是通过表内检索出的
ref
显示索引的哪一列被使用了,如果可能的话,是一个常量(const),哪些列或常量被用于查找索引列上的值 一般表示哪些字段被这个表使用了作为一个条件查询 表之间的引用
rows
根据表统计的信息及索引选用的情况,大致估算出找到所需的记录所需要读取的行数
Extra
包含不适合在其它列中显示但有十分重要的信息
- **Using filesort(严重) **出现这个表示索引内排序 优化器没有按照既定的索引执行,而是自动重新进行排序 所以一般如果是复合索引,要遵守最左前缀原则
- **Using temporary(最严重) **使用了临时表保存中间结果,MYSQL在对查询结果排序的时候使用临时表,常见于排序order by和分组查询group by 注意:要么不简建索引,要键建的话,group by或者order by一定要按照索引的顺序来,否则系统就要创建临时表,而临时表是很折腾系统性能的ine
- Using index 表示响相应的select操作中使用了覆盖索引,避免访问量数据行,效率不错, 如果同时出现Using where 表明索引被用来执行索引键值的查找,如果没有同时出现using where 表明索引用来读取数据而非执行查找动作
- **using join buffer ** 配置文件中的缓存配置可以调大些,增加它的缓冲大小
- **impossible where **表示where语句的值总是false,没法找到
索引优化
双表查询
- 如果是左连接 将索引建在右边,相反也是如此,因为我们只需要给关联数据建立索引,不需要查询全部数据再进行筛选
三表查询
SELECT * FROM class LEFT JOIN book ON class.card=book.card Left JOIN phone ON book.card=phone.card
这样的情况应该给book 的card字段和phone的card字段加上联合索引
Join语句优化
- 尽可能减少Join语句中的NestedLoop的循环总次数, **永远用小的结果集驱动大的结果集 **用上面的例子来说就是先查关联数据再查全部数据,所有我们给关联的数据加上索引
- 优化优先NestedLoop的内存循环
- 保证Join语句中被驱动表上Join条件字段已经被索引
- 当无法保证驱动表的Join条件字段被索引,且资源条件充足的情况下,可以适当调大JoinBuffer
注意
- 最佳左前缀原则
- 中间索引如果断了,后面索引不生效
- 范围条件右边的所有列,联合索引都不生效 范围之后全失效
- 不在索引列上做任何计算操作(计算,函数,(自动/手动类型转换)),这样会导致索引失效而转向全表扫描
- 在索引列上使用函数
select * from staffs where left(name,4)='Tonoy'
这样会失效索引
- 在索引列上使用函数
- 按需取数据,尽量少用
*
覆盖索引不写*
索引失效
-
Mysql在使用
!=
或者<>
都会导致索引失效从而全表扫描 -
is null
和is not null
也会导致索引失效is null
是直接连可能执行的索引都不会有 -
如果是
lilke
模糊查询,最好是在右边写%
在右边的%
相当于是范围查询,但是不同于范围查询的是,它可以踩着like
让后面的索引依旧有效 ,但如果%
放在左边,那该列索引和右边的索引都会失效, 如果非要写两边都是%
那么我们可以使用覆盖索引解决问题(按需索取) -
字符串不加单引号会导致索引失效 一般如果你设置的字段是varchar形,如果你查询的时候不加单引号,mysql优化器会帮你进行自动类型转换,但是这个转换是会导致索引失效的
-
少用
or
用它连接的时候,如果链接的字段不包含索引,那么会进行全表扫描Create
INDEX test_abc ON test(c1,c2,c3,c4)
where c1=a1 and c2=a2 and a4>c4 and a3=c3
这种情况是全部索引都会用到,因为底层是按照顺序来的
** 注意: 索引的两大功能 查找和排序, 查找可以不用先后顺序,只是中间不能有断层,而排序是要有先后顺序的,如果使用多个索引字段进行排序** order by
**那么如果不按照先后顺序的话,MySQL会进行内重排 **
** (但如果order by发生了顺序与索引不一致,但是,where条件上已经将字段变成常量,也就是前面查找已经用到该字段,那么Mysql会认为这排序的这个字段是一个常量,因此不会进行内重排),例如**
WHERE a1=c1 and a2= c2 order by a3,a2
这种情况不会出现 Usingfilesort
like
后面是常量开头,就说明该索引和后面的索引生效,如果like后面是 %
开头,那么当前索引包过后面索引都不会生效
小表驱动大表
- 当B的数据集小于A的数据集
select * from A where id in(select id from B)
使用in - 当A的数据集小于B的数据集
select * from A where exists(select1 from B where B.id=A.id)
使用exists
Order by
Order By满足两种情况,会使用Index方式排序:
- Order bY语句使用索引最做前列
- 使用Where 子句与Order By子句条件列组合满足索引最左前列
- 尽量不要OrderBy后面的索引字段升降不一
- 使用了OrderBy尽量不要使用*
Group By
- group by实质是先排序后进行分组,遵照索引建的最佳左前缀原则
- 当无法使用索引列,增大max_length_for_sort-data 参数的设置+增大sort_buffer_size参数的设置
- where高于having 能写在where限定的条件就不要取having 限定了
sort_buffer_size
不管采用单路算法还是多路算法,提高这个参数都会提高效率,当然,要根据系统能力去提高,因为这个参数是针对每个进程的
max_length_for_sort_data
提高这个参数,会增加用改进算法的概率,但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率
存储过程和函数
存储过程
存储过程相当于是SQL语句具有过程化语言的处理能力
创建存储过程
delimiter $ ##注意,我们要将分隔符从; 改成$ 使用该命令
create procedure 自定义名()
begin
SQL执行语句
end$
调用存储过程
call 存储过程的名字
查询存储过程
select name from mysql.proc where db=数据库名
实际上存储过程就是放在mysql库中的proc表中
show create porcedure 存储名
删除存储过程
drop procedure 存储名
Mysql的锁
- 共享锁: 支持并发的读操作,不支持写的操作
- 排外锁: 只支持单线程的写操作,不支持读
- 粒度锁:
- 表级锁 开销小,加锁快,不会出现死锁,锁定粒度大,发生锁的冲突高
- 行级锁 开销大,加锁慢,会出现死锁,锁定粒度最小,发生锁的冲突低,并发度也是最高的
- 页面锁 开销和加锁时间界于表锁和行锁之间,会出现死锁,并发度一般
Mybats中#和$的区别
使用#可以有一个预编译操作,这样可以有效防止SQL注入,防止用户操作数据库
预编译是Mysql内部直接先进行编译,执行的时候就直接将占位符替换成参数
由于预编译的时候,Mysql通过语法分析已经生成了语法树,这样只能该SQL的语法已经固定下来,SQL注入的数据只能作为参数,而不能再执行其它操作
但是如果是动态SQL的话,我们还是需要使用 比 如 动 态 表 和 数 据 字 段 , 一 般 o r d e r b y 和 G r o u p b y 后 面 的 字 段 名 还 是 要 使 用 {} 比如动态表和数据字段,一般order by和Group by后面的字段名还是要使用 比如动态表和数据字段,一般orderby和Groupby后面的字段名还是要使用{}
Mysql的行转列
SELECT USERNAME
MAX(if(COURSE='语文',num,0))'语文',
MAX(IF(COURSE='数学',num,0))'数学',
MAX(IF(COURSE='英语',num,0))'英语'
FROM TABLE
GROUP BY USERNAME
SELECT USERNAME
MAX(CASE COURSE WHEN '语文' THEN num ELSE 0 END)'语文',
MAX(CASE COURSE WHEN '数学' THEN num ELSE 0 END)'数学',
MAX(CASE COURSE WHEN '英语' THEN num ELSE 0 END)'英语'
FROM TABLE
GROUP BY USERNAME
三种范式
- 第一范式: 是对属性的原子性约束,要求字段具有原子性,不可再分解
- 第二范式: 在第一范式的基础上, 非主键字段不能出现部分依赖主键 解决: 消除复合主键就可以增加单列关键字
- 第三范式: 在第二范式的基础上, 非主键字段不能出现依赖传递,比如字段a依赖于主键,字段b依赖于字段a
四种特性
- 原子性 事务要保证原子操作,要么都成功,要么都不成功
- 隔离性 事务之间是相互隔离,互不影响的
- 一致性 事务前的数据一致事务后的数据也要保持一致
- 持久性 事务一旦提交,那么就是永久的,即使发生系统故障,也能够恢复
四种隔离级别
- Read Uncommiteed 允许脏读 其它事务只要修改了数据,即使未提交,另一个事务也能够看到修改后的数据
- Read Committed 只能读取提交后的数据,但是会出现不可重复读,也就是如果其他食物修改了数据,事务前读取和事务后读取会出现数据不一致的情况
- Repeated Read 无论其它事务是否修改提交了数据,在这个事务中看到的值始终不受影响
- Serializable 完全串行化,每次读都需要获得表级共享锁,读写相互会阻塞
MyISAM和InnoDB
- InnoDB支持事务,MyISAM不支持
- InnoDB支持行锁,
- InnoDB支持外键,MyISAM不支持
- InnoDB不支持全文搜索,MyISAM 支持,但是InnoDB可以使用插件实现,性能可能比MyISAM还好一点 5.6版本之后InnoDB开始支持全文检索,但不支持中英文混合
- MyISAM主要以查询以及插入为主, InnoDB主要以频繁修改以及涉及到安全性较高的应用
- MyISAM是非聚集索引,InnoDB是聚集性索引
- 对于自增长的字段,InnoDB必须包含只有该字段的索引, MyISAM可以有多个字段组成的复合索引
delete drop truncate的区别
- truncate和delete只删除数据,不删除表结构,drop删除表结构,并且释放所占用的空间
- 删数据的速度: drop>truncate>delete
- delete属于DML语言,需要事务管理,commit之后才会生效,drop和truncate属于DDL语言,操作立即生效,不可回滚
char和varchar的区别
- char 当数据字节小于m,那么则会自动用空格补全
- varchar 当数据字节小于m,每个数据也只会占用刚好的字节,再加上一个字节作为记录长度