MYSQL思维导图
2023-01-15 15:16:51 2 举报
AI智能生成
用思维导图方式来整理mysql相关技术内容
作者其他创作
大纲/内容
深入理解MYSQL索引底层数据结构和算法
索引的定义:索引是帮助MYSQL高效的获取排好序的数据的数据结构。<br>
索引的数据结构
二叉树<br>
树高度普通二叉树最坏n.<br>
红黑树<br>
二叉平衡树,树高度logn+1.<br>以20000000条数据为例,h=log20000000=25<br>
Hash表<br>
对索引的key进行一次hash计算就可以定位出数据存储的位置<br>
很多时候Hash索引要比B+Tree更高效<br>
仅能满足=、in,不支持范围查询<br>
hash冲突问题(数组+链表)<br>
B-Tree<br>
叶子节点具有相同的深度,叶节点的指针为空。
所有索引的元素不重复<br>
节点中的数据索引从左到右递增排列<br>
B+Tree<br>
非叶子节点不存储data,只存储索引(冗余),索引空白部分指向其他页的地址,可以放更多的索引,<br>
查看页大小:show global status like 'innodb_page_size';<br>
存储引擎
MyISAM:索引文件和数据文件是分离的,属于非聚集索引
test.frm 存放表结构等信息<br>
test.MYD 存放数据
test.MYI 存放索引
InnoDB:索引文件和数据文件是不分离的,属于聚集索引<br>
表结构文件本身就是按B+Tree组织的一个索引结构文件。<br>
叶子节点包含了完整的数据记录。<br>
建议InnoDB表必须建主键,并且推荐使用整型的自增主键。<br>
非主键索引(二级索引)结构中的叶子节点存储的是主键值。<br>
保持一致性,若修改记录内容,只需要改一次主键索引中的数据,再更新非主键索引即可。<br>
节省空间,只用保留一份数据即可,若索引中全都要有完整数据,浪费硬盘。<br>
联合索引底层数据结构:多个字段组成的联合索引,按字段顺序进行排序<br>
遵循索引最左前缀原理<br>
如果第一个字段是范围查询需要单独建一个索引;<br>
在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边;
Explain详解与索引最佳实践<br>
explain工具介绍<br>
模拟优化器执行SQL语句
分析你的查询语句或是结构的性能瓶颈<br>
执行查询会返回执行计划的信息
from 中包含子查询,仍会执行该子查询,将结果放入临时表中
explain 两个变种,MySQL5.7以后不用了
explain extended
explain partitions
explain中的列
id
有几个select就有几个id,id越大越先执行
select_type
simple:简单查询。查询不包含子查询和union
primary:复杂查询中最外层的 select
subquery:子查询,包含在 select 中的子查询(不在 from 子句中)
derived:包含在 from 子句中的子查询。MySQL会将结果存放在一个临时表中,也称为派生表(derived的英文含<br>义:衍生)
union:在 union 中的第二个和随后的 select
table
表示 explain 的一行正在访问哪个表,当 from 子句中有子查询时,table列是 <derivenN> 格式,<br>
有 union 时,UNION RESULT 的 table 列的值为<union1,2
type
system<br>
system是const的特例<br>
const<br>
读取一次<br>
eq_ref<br>
primary key 或 unique key 索引的所有部分被连接使用 ,最多只会返回一条符合条件的记录。这可能是在<br>const 之外最好的联接类型了,简单的 select 查询不会出现这种 type。
ref<br>
相比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀,索引要和某个值相比较,可能会<br>找到多个符合条件的行<br>
range<br>
范围扫描通常出现在 in(), between ,> ,<, >= 等操作中。使用一个索引来检索给定范围的行
index<br>
通常扫描二级索引,扫描全索引拿到结果
all
全表扫描,可以考虑通过增加索引进行优化<br>
possible_keys<br>
这一列显示查询可能使用哪些索引来查找
key<br>
NULL<br>
没有使用索引
force index<br>
强制使用possible_keys中的索引<br>
ignore index
忽略使用possible_keys中的索引<br>
key_len
列显示了mysql在索引里使用的字节数,通过这个值可以算出具体使用了索引中的哪些列<br>
字符串<br>
char(n):如果存汉字长度就是 3n 字节<br>
varchar(n):如果存汉字则长度是 3n + 2 字节
数值类型
tinyint:1字节
smallint:2字节
int:4字节
bigint:8字节
时间类型
date:3字节<br>
timestamp:4字节
datetime:8字节<br>
ref
这一列显示了在key列记录的索引中,表查找值所用到的列或常量,常见的有:const(常量),字段名(例:film.id)<br>
row
是mysql估计要读取并检测的行数,并非最终结果集的条数<br>
extra
Using index:使用覆盖索引
Using where:使用 where 语句来处理结果,并且查询的列未被索引覆盖
Using index condition:查询的列不完全被索引覆盖,where条件中是一个前导列的范围
Using temporary:mysql需要创建一张临时表来处理查询
Using filesort:将用外部排序而不是索引排序
Select tables optimized away:使用某些聚合函数(比如 max、min)来访问存在索引的某个字段
索引最佳实践<br>
全值匹配
最左前缀法则
不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
存储引擎不能使用索引中范围条件右边的列
尽量使用覆盖索引(只访问索引的查询(索引列包含查询列)),减少 select * 语句
mysql在使用不等于(!=或者<>),not in ,not exists 的时候无法使用索引会导致全表扫描<br>< 小于、 > 大于、 <=、>= 这些,mysql内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引
is null,is not null 一般情况下也无法使用索引
like以通配符开头('$abc...')mysql索引失效会变成全表扫描操作
字符串不加单引号索引失效
少用or或in,用它查询时,mysql不一定使用索引,mysql内部优化器会根据检索比例、表大小等多个因素整体评<br>估是否使用索引
范围查询优化
SQL底层执行原理详解<br>
客户端层<br>
命令行连接,linux或者windows下安装的mysql客户端,通过命令行连接。<br>
JDBC,java通过jdbc连接数据库。<br>
navicat等连接工具,可视化。<br>
服务端层
连接器:管理连接与权限校验。
mysql -h 数据库地址 -u 用户 -p 密码 -P 端口<br>
show processlist;
kill Id;
show global variables like "wait_timeout";
默认8小时,28800秒<br>
set global wait_timeout=28800;<br>
查询缓存<br>
MySQL8已经移除<br>
词法分析器:词法分析,语法分析<br>
词法分析<br>
语法分析<br>
语义分析<br>
构造执行树<br>
生产执行计划<br>
计划的执行<br>
优化器:执行计划生成索引选择<br>
执行器:调用引擎接口获取查询结果<br>
存储引擎层<br>
Innodb:最常用,从 MySQL 5.5.5 版本开始成为了默认存储引擎。<br>
MyISAM<br>
memory:不常用<br>
bin-log归档<br>
开启binlog功能,配置my.cnf
log-bin=/usr/local/mysql/data/binlog/mysql-bin<br>
server-id=123454<br>
binlog-format=ROW(有3种statement,row,mixed),推荐row<br>
sync-binlog=1<br>
#表示每1次执行写入就与硬盘同步,会影响性能,<br>为0时表示,事务提交时mysql不做刷盘操作,由系统决定<br>
binlog命令
show variables like '%log_bin%'; 查看bin-log是否开启<br>
flush logs; 会多一个最新的bin-log日志
show master status; 查看最后一个bin-log日志的相关信息
reset master; 清空所有的bin-log日志
查看binlog内容
/usr/local/mysql/bin/mysqlbinlog <br>--no-defaults /usr/local/mysql/data/binlog/mysql-bin.000001<br>
寻找begin,commit这种关键词信息,只要在binlog当中看到了,<br>你就可以理解为begin-commit之间的信息是一个完整的事务逻辑,然后再根据位置position判断恢复即可<br>
Mysql索引优化实战1<br>
综合例子
联合索引第一个字段用范围不会走索引
explain select name,age,position from employees<br>where name > 'LiLei' and age = 22 and position = 'manager';
强制走索引
explain select * from employees<br>force index(idx_name_age_position)<br>where name > 'LiLei' and age = 22 and position = 'manager';
覆盖索引优化
explain select name,age,position from employees where name > 'LiLei' and age = 22 and position = 'manager';<br>
in和or在表数据比较大的情况会走索引,<br>在表记录不多的情况下会选择全表扫描,MySQL8,记录不多也会走索引<br>
like KK% 一般情况都会走索引
explain select * from employees where name like 'LiLei%' and age = 22 and position = 'manager';
索引下推(Index Condition Pushdown,ICP),like用到了索引下推优化。
MySQL5.6以前,先以LiLei为头,查出所有索引,拿这个索引对应主键逐个回表,找出数据后再对比age和position.
MySQL5.6以后,先拿到索引判断三个字段是否匹配,找到符合的索引,再回表,减少了回表的次数。
针对二级索引有效,如果是主键索引,无效。<br>
trace工具(追踪工具)
开启: set session optimizer_trace="enabled=on",end_markers_in_json=on;<br>
select * from employees where name > 'a' order by position;<br>
SELECT * FROM information_schema.OPTIMIZER_TRACE;<br>
关闭:set session optimizer_trace="enabled=off"; <br>
常见SQL深入优化<br>
1、MySQL支持两种方式的排序filesort和index,Using index是指MySQL扫描索引本身完成排序。<br>index效率高,filesort效率低。<br>
2、order by满足两种情况会使用Using index。<br>1) order by语句使用索引最左前列。<br>2) 使用where子句与order by子句条件列组合满足索引最左前列。
3、尽量在索引列上完成排序,遵循索引建立(索引创建的顺序)时的最左前缀法则。
4、如果order by的条件不在索引列上,就会产生Using filesort。
5、能用覆盖索引尽量用覆盖索引<br>
6、group by与order by很类似,其实质是先排序后分组,遵照索引创建顺序的最左前缀法则。<br>对于group by的优化如果不需要排序的可以加上order by null禁止排序。<br>注意,where高于having,能写在where中的限定条件就不要去having限定了。<br>
filesort文件排序<br>
max_length_for_sort_data 系统变量 1024字节,1K
单路排序:单条记录字段的总长度小于1K,一次性将所有字段取出进行排序。<br>
双路排序: 单条记录字段的总长度大于1K,只将排序字段和主键取出,排序后再回表。<br>
索引设计原则
代码先行,索引后上<br>
联合索引尽量覆盖条件<br>
不要在小基数数字段上建立索引<br>
长字符串可以采用前缀索引: key index(name(20),age,position),研究表明再长的字符串,前20位基本可以进行排序了。
where和order by冲突时,优先优化where.
基于慢SQL查询优化<br>
http://note.youdao.com/noteshare?id=c71f1e66b7f91dab989a9d3a7c8ceb8e&sub=0B91DF863FB846AA9A1CDDF431402C7B
Mysql索引优化实战2
分页查询优化
根据自增且连续的主键排序的分页查询<br>
主键自增且连续<br>结果是按照主键排序的<br>
根据非主键字段排序的分页查询<br>
让排序时返回的字段尽可能少
Join关联查询优化
嵌套循环连接 Nested-Loop Join(NLJ) 算法<br>
循环读取驱动表,根据关联条件,取出被驱动表数据,组成结果集<br>
基于块的嵌套循环连接 Block Nested-Loop Join(BNL)算法<br>
把驱动表的数据读入到 join_buffer 中,然后扫描被驱动表,把被驱动表每一行取出来跟 join_buffer 中的数据做对比
对于关联sql的优化
关联字段加索引<br>
小表驱动大表
in和exsits优化
where中时小表则in
from中小表,则exsits<br>
count(*)查询优化
字段有索引<br>
count(*)≈count(1)>count(字段)>count(主键 id)
字段无索引<br>
count(*)≈count(1)>count(主键 id)>count(字段)
常用优化方式
查询mysql自己维护的总行数
show table status
将总数维护到Redis里<br>
增加数据库计数表
阿里巴巴Mysql规范解读<br>
数值类型
如果整形数据没有负数,如ID号,建议指定为UNSIGNED无符号类型,容量可以扩大一倍。<br>
建议使用TINYINT代替ENUM、BITENUM、SET。<br>
避免使用整数的显示宽度(参看文档最后),也就是说,不要用INT(10)类似的方法指定字段显示宽度,直接用INT<br>
DECIMAL最适合保存准确度要求高,而且用于计算的数据,比如价格。但是在使用DECIMAL类型的时候,注意长度设置<br>
建议使用整形类型来运算和存储实数,方法是,实数乘以相应的倍数后再操作<br>
整数通常是最佳的数据类型,因为它速度快,并且能使用AUTO_INCREMENT<br>
日期和时间
MySQL能存储的最小时间粒度为秒
建议用DATE数据类型来保存日期。MySQL中默认的日期格式是yyyy-mm-dd<br>
用MySQL的内建类型DATE、TIME、DATETIME来存储时间,而不是使用字符串<br>
当数据格式为TIMESTAMP和DATETIME时,可以用CURRENT_TIMESTAMP作为默认(MySQL5.6以后),<br>MySQL会自动返回记录插入的确切时间
TIMESTAMP是UTC时间戳,与时区相关<br>
DATETIME的存储格式是一个YYYYMMDD HH:MM:SS的整数,与时区无关,你存了什么,读出来就是什么<br>
除非有特殊需求,一般的公司建议使用TIMESTAMP,它比DATETIME更节约空间,<br>但是像阿里这样的公司一般会用DATETIME,因为不用考虑TIMESTAMP将来的时间上限问题
有时人们把Unix的时间戳保存为整数值,但是这通常没有任何好处,这种格式处理起来不太方便,我们并不推荐它
字符串
字符串的长度相差较大用VARCHAR;字符串短,且所有值都接近一个长度用CHAR<br>
CHAR和VARCHAR适用于包括人名、邮政编码、电话号码和不超过255个字符长度的任意字母数字组合。<br>那些要用来计算的数字不要用VARCHAR类型保存,因为可能会导致一些与计算相关的问题。换句话说,可能影响到计算的准确性和完整性
尽量少用BLOB和TEXT,如果实在要用可以考虑将BLOB和TEXT字段单独存一张表,用id关联<br>
BLOB系列存储二进制字符串,与字符集无关。TEXT系列存储非二进制字符串,与字符集相关
BLOB和TEXT都不能有默认值
深入理解Mysql事务隔离级别与锁机制
事务及其ACID属性
原子性(Atomicity) :事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
一致性(Consistent) :在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规<br>则都必须应用于事务的修改,以保持数据的完整性。
隔离性(Isolation) :数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独<br>立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。<br>
持久性(Durable) :事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。<br>
并发事务处理带来的问题
更新丢失(Lost Update)或脏写
最后的更新覆盖了由其他事务所做的更新
脏读(Dirty Reads)
事务A读取到了事务B已经修改但尚未提交的数据
不可重复读(Non-Repeatable Reads)
事务A内部的相同查询语句在不同时刻读出的结果不一致,不符合隔离性
幻读(Phantom Reads)
事务A读取到了事务B提交的新增数据,不符合隔离性
事务隔离级别
读未提交(Read uncommitted)
可能有脏读,不可重复读,幻读<br>
读已提交(Read committed)<br>
可能有不可重复读,幻读
oracle默认
可重复读(Repeatable read)<br>
可能有幻读
msyql默认
可串行化(Serializable)
没有问题,但是效率很低,通常不用
锁分类
从性能上
乐观锁
用版本号对比来实现
悲观锁
操作类型上
读锁
共享锁
S锁(Shared)
写锁<br>
排他锁
X锁(eXclusive)
数据操作粒度上
表锁
行锁
间隙锁
锁的就是两个值之间的空隙
临键锁
是行锁与间隙锁的组合
死锁
子主题
锁优化建议
尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁
合理设计索引,尽量缩小锁的范围
尽可能减少检索条件范围,避免间隙锁
尽量控制事务大小,减少锁定资源量和时间长度,涉及事务加锁的sql尽量放在事务最后执行<br>
尽可能低级别事务隔离<br>
深入理解MVCC与BufferPool缓存机制
MVCC多版本并发控制
undo日志版本链
一行数据被多个事务依次修改过后,在每个事务修改完后,Mysql会保留修改前的数据undo回滚<br>日志,并且用两个隐藏字段trx_id和roll_pointer把这些undo日志串联起来形成一个历史记录版本链
一致性视图read view机制<br>
读已提交RC<br>
每次查询前都会生成<br>
可重复读RR
第一次查询生成后就不变了
Innodb引擎SQL执行的BufferPool缓存机制<br>
在内存中缓存起来,可以保证每个更新请求都是更新内存BufferPool,然后顺序写日志文件<br>
InnoDB引擎底层存储和缓存原理以及MySQL8新特性<br>
InnoDB存储引擎设计了4种不同类型的行格式
Compact<br>
记录的额外信息<br>
变长字段长度列表<br>
NULL值列表<br>
每个允许为null字段,1代表为null
记录头信息:5字节,40位<br>
预留位1 1 没有使用<br>
预留位2 1 没有使用<br>
delete_mask 1 标记该记录是否被删除
min_rec_mask 1 B+树的每层非叶子节点中的最小记录都会添加该标记<br>
n_owned 4 表示当前记录拥有的记录数
heap_no 13 表示当前记录在页的位置信息
record_type 3 表示当前记录的类型,0表示普通记录,1表示B+树非叶子节点记录,2表示最小记录,3表示最大记录
next_record 16 表示下一条记录的相对位置<br>
隐藏列<br>
DB_ROW_ID(row_id):非必须,6字节,表示行ID,唯一标识一条记录<br>
先使用用户定义的主键,然后使用唯一索引,最后是自增的row_id<br>
DB_TRX_ID(trx_id):必须,6字节,表示事务ID<br>
DB_ROLL_PTR(roll_ptr):必须,7字节,表示回滚指针<br>
记录的真实信息<br>
列1-列n
Redundant<br>
Redundant行格式是MySQL5.0之前用的一种行格式,不予深究。<br>
Dynamic<br>
MySQL5.7的默认行格式就是Dynamic<br>
Compressed<br>
处理行溢出数据时,会采用压缩算法对页面进行压缩,以节省空间。<br>
索引页格式<br>
File Header 文件头部 38字节 页的一些通用信息<br>
Page Header 页面头部 56字节 数据页专有的一些信息<br>
Infimum + Supremum 最小记录和最大记录 26字节 两个虚拟的行记录<br>
User Records 用户记录 大小不确定 实际存储的行记录内容<br>
Free Space 空闲空间 大小不确定 页中尚未使用的空间
Page Directory 页面目录 大小不确定 页中的某些记录的相对位置<br>
File Trailer 文件尾部 8字节 校验页是否完整
InnoDB的体系结构
内存结构
Buffer Pool<br>
缓冲池,128M,建议给物理内存的60%<br>
控制块<br>
内存碎片
缓存页<br>
free链表管理<br>
flush链表管理<br>
LRU链表管理<br>
Change Buffer<br>
Log Buffer<br>
磁盘结构
系统表空间<br>
数据字典<br>
双写缓冲区<br>
Change Buffer<br>
Undo日志<br>
独立表空间<br>
页节点段:逻辑上的
组<br>
256个区
区:物理上连续,减少随机I/O
64个页<br>
页
16K,若干记录
非页节点段
回滚段
通用表空间<br>
临时表空间<br>
Undo表空间<br>
Redo日志<br>
0 条评论
下一页