- 了解SQL语句在Mysql内部是怎么流转的?
- MySQL的体系结构
- MySQL存储方式:
- SQL语句执行过程
- 1 服务层
- 2 核心层(mysqld) 指的是CPU和内存部分
- 3 存储引擎层 硬盘
- SQL语句执行步骤
- InnoDB buffer pool原理
- 索引设计
- 常见索引模型
- 表连接
- 表连接的执行计划
- 常用的表连接方式
- 辅助索引范围扫描的优化方式
- MySQL函数
- 三种函数
- string函数
- Date and Time函数
- numeric函数
- select语句中的聚合函数
- union
- group by
- 执行计划的阅读
- MySQL中的锁
- 全局锁
- 表级锁
- 表锁
- 元数据锁 MDL(metadata lock)
- 行级锁
- 总结
- 主从复制用途
- 补充知识
- 在MySQL的innodb引擎中如何查看未完成的事务
- 查看mysql当前活跃的并发连接数
- mysql的执行计划是在什么地方生成的?
- 如何压缩并且重建表的索引
- mysql数据类型
- SQL循环语句
- key_len表示被使用索引列的长度
- lenngth()表示字节数;char_length()表示字符数
- sum()和avg()函数的限制是
- 加载表上的锁
- 能观察到锁的信息
redis缓存?抗住数据的高并发
MySQL主从架构:主要数据库接收用户传过来的数据变化,从数据库接收主数据库中的变化
MySQL是一个千万级的数据结构,一张表的数据量控制到几千万
要存储大数据量——采用分布式存储,一张表分散到几个数据库中去存储
要学习分析数据库内部的性能
- MySQL语句是单进程多线程的,所有的解析优化都是一个进程完成的
- 主键是非空且唯一的
- ENGINE:表中的主键引擎方式。MySQL5.5之后默认创建的数据库引擎为ENGINE=InnoDB。若没有指定主键,则MySQL会找第一个非空且唯一的数据作为主键。若都没有,则会默认建立一个隐藏列,这个隐藏列就会作为非空且唯一的6字节主键索引。因此不要求必须要有非空且唯一列。
-
InnoDB(聚簇cluster table):内部是按照主键的形式存在的,主键所在的叶子位置就是这条数据的位置。要是非主键索引来查数据,会先找到非主键指向的数据行,然后由这行数据中的主键索引再次查询找到这条数据的索引位置
支持事务
考虑索引的性能需要考虑树的高度,当需要取出的数据量比较小的时候才适合用索引查找 -
MylSAM(非聚簇):是一个典型的堆表,来一个数据堆存储,来一个数据堆存储——逐渐淘汰 不支持事务
由主键索引的树结构进行折半查找,找到相应的员工号所在叶子节点,里边存员工号的值与具体所在的行号指针,指针指向这条数据所在的行
从SQL语句执行过程开始:
Commander Sispatcher:命令分发器 Query Cache Module:缓存,方便重复取值——一般不用,8.0之后版本就没有了
|
Parser:解析SQL语句——下边有多个优化的分支
|
Access Control Module:有没有访问权限
|
Table Manager
|
Abstracted Storage Engine Module:抽象存储引擎——InnoDB、MylSAM
包括SQL接口、解析器、优化
3 存储引擎层 硬盘 SQL语句执行步骤- 解析: 根据SQL语句第一个单词把SQL语句路由到相应的位置 耗费CPU
- 词法解析:看你要查的这个表的这个列存在不存在
- 语法解析:看SQL语句语法对不对
- 优化器: 生成真正的执行计划
- SELECT——Optimizer
- UPDATE,etc.——Table Modification Module (Update etc.)
MySQL不缓存执行计划,用的是硬解析,需要CPU去解析
MySQL8.0之后query cache缓存被去掉了
- 执行器: 到不同的存储引擎中把数据查取出来 耗费的硬盘资源
MySQL数据文件:一张表一个文件 比如emp表会有文件emp.ibd存数据, 文件emp.frm存表结构文件
InnoDB buffer pool是一个大的存储空间 - 获取: 若为取数据则直接取,若有排序则还需进行排序再获取数据
- MySQL数据访问状态:
idle空闲
active
session会话
-
InnoDB buffer pool即InnoDB存储引擎内存模型——缓冲池
InnoDB buffer pool存储的是索引和数据 -
InnoDB buffer pool以LRU(Latest Recent Use,最近最少使用)链表的方式存储数据
-
作用:缓存索引和数据
-
warm up预热,让表缓存到内存池InnoDB buffer pool,防止系统崩溃
-
Instance参数:设置CPU数量
%:通配符代表任意字符串 -
每个用户创建之后就会生成一些内存缓存:sort buffer、join buffer、mult-range read buffer(MRR)
-
内存中会产生脏数据dirty date
写脏数据和是否写commit没有关系,取决于当前磁盘的IO压力
commit提交事务:将日志永久的保存到磁盘里
rollback取消事务事务
事务:就是对数据做修改,最后以确认commit或者回滚rollback为结束。
事务的四大特性:原子性(commit 和 rollback,要么都成功要么都失败)、一致性(UNDO保证)、隔离性(锁)、持久性(redo 日志) -
了解InnoDB buffer pool的两个进程:
IO write thread *n:写数据线程 线程的个数不能超过CPU的核数
IO read thread *n:读数据线程 -
ibdata1文件:存放undo日志,里边存数据被修改之前的值【前镜像】,UNDO里边的值在commit之后就失效了
undo日志作用:
(1)没有提交或者回滚的时候,做一致性读取【前镜像】
(2)回滚rollback之后,就支持事务的回滚 -
log buffer日志缓存:当缓存内容达1M就会存到磁盘
ib_logfile0文件、ib_logfile1文件:保存REDO日志,redo日志太小则会导致数据库hang住
redo日志: 当数据库对数据做修改的时候,需要把数据页从磁盘读到buffer pool中,然后在buffer pool中进行修改,那么这个时候buffer pool中的数据页就与磁盘上的数据页内容不一致,称buffer pool的数据页为dirty page 脏数据,redo日志记录相应的数据修改内容(记录日志是顺序IO),因此当DB发生crush的情况,回复DB的时候,可以根据redor日志将这个文件的记录内容重新应用到磁盘文件,使得数据保持一致。
数据库服务器宕机,如果发生其他故障导致尚有脏页未写入磁盘的场景,也是可以通过redo log恢复的。
redo 日志不可以归档!
log buffer 进程:log thread
数据库软件设计第二大原则:日志先行Write ahead log(WAL),日志记录先一切操作
断电后内存中的数据丢失,磁盘中的数据还在
commit操作就是将log buffer中的日志刷到redo log里,这叫落盘,redo log主要节省的是随机写磁盘的IO消耗(转成顺序写)
要尽量避免随机读,物理损耗太大,减少commit提交量
参数:inndb_flush_log_at_trx_commit:为1则严格执行commit一次刷一次盘; 为0则每秒钟刷一次盘(有可能丢失1秒钟的数据),可以提高事务的执行速度,因为每攒够1秒钟就刷一次数据; 为2则每commit一次则把其存到操作系统的一个缓存区里,每1秒钟再把缓存的commit数据统一刷到磁盘里。 -
redo log执行流程图:
-
关系型数据库瓶颈:并发。 多个生产者应对一个消费者
| mysqld 内存+CPU | log buffer |
|---|---|
| engine 存储引擎 | redo log |
- 在MySQL的innodb引擎中如何查看未完成的事务:通过information_schema.innodb_trx视图
- length和char_length的区别:
select length(“xxx”):返回存储这个字符串所占的字节数
utf8编码:一个汉字三个字节,一个数字或字母一个字节。
gbk编码:一个汉字两个字节,一个数字或字母一个字节。
select char_length(“xxx”):返回存储这个字符串所占的字符数,字母、数字、汉字都当成一个字符处理。按照参数值character_set_client=xxx来计算,若=utf8则是一个字符占3个字节,若=latin1则一个字符占一个字节
数据库的变量合集:show variables like '%xxx%'
varchar是变长类型
索引设计- 索引目的:提高查询效率,就像书的目录一样
主键索引也叫聚簇索引
-
哈希表索引
插入速度快;没有办法做范围性查询 -
有序数组
插入更新速度慢;支持范围查询;
只适合一些历史表 -
二叉树
把折半查找形象化了;
查找速率最快;
只能挂两个分支,数据越多树层数越多,效率越低,因此受限制,数据库不使用 -
N叉搜索树 也叫 B+树 也叫 平衡搜索树
Balance平衡
N叉树中的N取决于数据块的大小
树高是多少每次取这个数据就需要经过多少次IO -
InnoDB索引模型:
主键ID索引:聚簇索引
K值索引:二级索引、辅助索引- 若语句是主键查询方式,则只需要搜索ID这颗B+树;
- 若语句是普通索引查询方式,则需要先搜索K索引树,得到ID值,再到ID索引树搜索一次,这个过程称为回表
若select的内容只包含k和ID,则不需要回表
-
InnoDB索引维护:插入新值得时候需要做必要的维护。比如在建表的时候加上自增主键。
不涉及到挪动其他记录,也不会触发叶子结点的分裂
若用整型(int)做主键,则只要4个字节,若长整型(bigint)则是8个字节
主键长度越小,普通索引的叶子结点越小,普通索引占用的空间也就越小。 -
select * from T where k between 3 and 5需要执行6次树的搜索操作。每次搜索都需要从根节点开始搜索
图:
3 7
1 2 3 5 6
100 200 300 500 600 -
覆盖索引 —— 常用的索引优化手段
目的:减少回表
设计表的时候索引有(name, age)的时候是一个联合索引,实则有三列,因为主键id也暗自包括在里边了,(name, age)就是把年龄和姓名拼接起来存储。
联合索引必须第一列出现的时候才会有效,最左列的分支将数据先定义到某一个分支,然后才去匹配后边的列。——最左前缀原则。当通配符%出现在最前边的时候,索引失效。 -
select concat与select ws_concat的区别
concat(str1,str2,...):concat是直接将字符串进行拼接
concat_ws(separator,str1,str2,...):ws_concat会把第一个字符当做分隔符,然后再用该分隔符将字符串连接起来 -
user()和current_user()
user()返回当前登陆的用户user
current_user()返回这个用户user在数据库mysql.user表里对应的账号 -
获得当前日期:curdate()
date是数据类型不是函数 -
收集统计信息:analyze table test1;(test1表示表名)
查看统计信息:show index from test1; -
唯一索引与普通索引
(1)若创建的是唯一索引,则每次插入值的时候都会走一次索引,查询是否有重复的数据。唯一索引的每一列都是不一样的。由于索引定义了唯一性,在查找到第一个满足条件的记录后,就会停止检索,返回找到的数据
(2)普通索引:在查找到满足条件的第一个记录(5,500)之后,还需要查找下一个记录,直到碰到第一个不满足k=5条件的记录
唯一索引,修改时要判断唯一性,change buffer失效
普通索引,修改时不需要读取记录
(3)性能:普通索引比唯一索引多做了一次内存搜素和判断,性能相差不大
(4)区别:数据不在内存时候,唯一索引要加载数据判断唯一性,普通直接存入change buffer,返回 -
Innodb引擎的change buffer
change buffer 是对数据进行修改的时候(insert、update、delete)的时候将修改优先写入change buffer,减少磁盘的随机IO消耗
change buffer是Innodb buffer pool特有的
把修改先缓存到change buffer,当数据缓存到一定程度之后,再将修改进行分类刷到磁盘,减少物理移动的次数, changebuffer因为减少了随机读磁盘的IO消耗,所以提升了更新数据的性能
若要修改的列有唯一索引,则change buffer失效
change buffer劣势: 不适合于马上写就要马上读的操作;不适合写多读少的业务;不适合账单类、日志类业务
delimiter指令:重新定义分号;,若没有重新定义则每句的结尾就是以分号结束,重新定义可以自定义结束符号
call指令:调用存储过程
表连接 表连接的执行计划-- 假设t1有100条数据;t2有1000条数据 -- 表连接查询语句: select * from t1 join t2 on(t1.a=t2.a) -- 查看表连接这个操作的执行计划指令:`explain` explain select * from t1 join t2 on(t1.a=t2.a)
force index:强制让表查询走索引
!、 <> 、 is null、 is not null不走索引
常用的表连接方式Nested-Loop join:嵌套循环连接
- Index Nested-Loop Join:
Index:有索引
(1)先选一个数据量比较小的表作为驱动表,对表t1做一次全表扫描,前提是被驱动表里边有索引;
(2)每从t1里拿出一行,就在被驱动表t2中进行1次索引查找,一共100次;
(3)返回结果; - Simple Nested-Loop join: 即被驱动表t2没有索引,需要做M*N次扫描
Simple:没索引
优化手段:Block-Nested-Loop join: Block:分块
优化方法:先把驱动表缓存到join buffer里,这个区域属于内存区域这样做全表扫描的时候就非常快,然后再用被驱动表去扫描驱动表 扫描的数量个数没有变,优化的点在于驱动表放在了内存区域中的join buffer里,这样查询速度快了。
Block NLJ问题:用join buffer将其中的一个较小的表缓存到join buffer中,若两个表的数据都比较大,则需要将join buffer的大小调大一些,方便使用。
join buffer不适合高并发的场景。
- 四种表连接的优化方法:
Batched Key Access(BKA) > Index NLJ > Block NLJ> Simple NLJ
怎么使用:设置优化方法参数set optimizer_switch = 【要启动的优化方法】
要启动的优化方法中:
block_nested_loop默认是on的;1
bached_key_access默认是off的,要打开的话还需要把MRR的设置也启动,即 mrr=on, mrr_cost_based=off;
MRR(multi range read):优化器对二级辅助索引扫描的优化方法
MRR也是每次用户一登入都会给用户分配的一个内存的buffer
show variables like '%buffer%':查看参数中有buffer字段的变量都有哪些
read_rnd_buffer_size:存储辅助索引范围扫描后的结果,对其进行分类优化
optimizer_switch:控制优化器能够使用什么算法,用参数指定
当二级索引范围扫描比较多的时候,就可以启动MRR扫描:set optimizer_switch = 'mrr=on, mrr_cost_based=off'
表连接的Index NLJ就可以用到这个MRR优化方法——Batched Key Access(BKA)
将辅助索引主键缓存到join buffer里
- 用到join buffer的表连接优化方式有: BNL、BKA
- String:处理字符串
- Data and Time:处理时间类型
- Numeric:处理数字
- 获取字符串的长度:
select length('xxx');返回字节数(byte)
select char_length('xxx');返回字符个数 - instr(string, subdtring):返回substring在string中的位置,从1开始数
- strcmp(string1, string2):返回两个字符串的比较。返回值0:相同;-1:string1比string2要小;1:string1比string2大
- 字符串拼接:
concat('str1', 'str2', 'str3'):将字符串拼接在一起
concat_ws('sep', 'str1', 'str2', 'str3'):用分隔符sep将字符串拼接起来 - reverse('xxx'):把字符串取反
- 截取字符串:
left('xxx', num):从左到右截取num个字符
right('xxx', num):从右到左截取num个字符 - 变大小写:
lower():全部转换成小写
upper():全部转换呈大写 - 位数不足则补位
lpad('MySQL', 9, '.'):’…MySQL’
lpad('MySQL', 9, '.'):‘MySQL…’ - 从固定位置截取指定位数的字符串
substring('xxxxxx', 位置,要截取的位数) . - 用字符s分割字符串,取出其中的前n个字符串,n为正数则从左往右数的前n个字符串,n为负数则为从右往左数的前n个字符串
substring_index('xxx1sxxx2', 's', 1):xxx1 - 去除空格或者指定的字符
trim( MySQL ):‘MySQL’
trim('x' from 'xxxMySQLxxx'):‘MySQL’去除字符串中指定的字符
leading去掉头部的空格
时间字段尽量使用时间类型,对时间类型数据的加减操作也是
data
| 函数 | 语法 |
|---|---|
| 获取当前的日期以及时间 | select now() |
| 获取当前的日期 | curdate() |
| 获取当前的时间 | select curtime() |
| 获取年 | year(data_expression日期类型的参数) |
| 获取月 | monthname(data_expression) |
| 获取日 | dayname(data_expression) |
| 获取时、分、秒 | hour(data_expression)、minute(data_expression)、second(data_expression) |
| 数据类型 | 默认的格式 |
|---|---|
| DATE | YYYY-MM-DD |
| TIME | HH:MM:SS |
| DATETIME | YYYY-MM-DD HH:MM:SS |
| TIMESTAMP | YYYY-MM-DD HH:MM:SS |
| YEAR | YYYY |
-
四舍五入
round():四舍五入到整数
round(num, n):四舍五入到小数点后的第n位
特殊:round(23.298, -1) = 20 -
截取:
truncate(num, n):不考虑四舍五入的截取小数点后的n位
特殊:truncate(122.999, -2) = 100 -
无论大小均进数:ceiling()、ceil()
-
查看当前用户:
user():查看当前登录的用户
current_user():查看当前用户在mysql.user数据表里的位置,即数据字典里对应的内容
create user 'jack'@'%' identified by 'jack'; -- 创建用户jack可以从任意主机登录 mysql -h localhost -p3306 -ujack -p jack; -- 登录创建的用户select语句中的聚合函数
- count():计数 select count(*) from tble1; – 统计数据的条数
- sum():求和
- group by:分组
- having:对group by之后的行再进行条件筛选,即对分组再进行过滤
- group_concat():分组后把每组的内容放在group_concat()这个列里
语法:group_concat( [DISTINCT] 要连接的字段 [Order BY 排序字段 ASC/DESC] [Separator ‘分隔符’] )
例如:以id分组,把price字段的值在一行打印出来,分号分隔select id,group_concat(price separator ';') from goods group by id;
union:并集去重, 需要先缓存在temparary buffer临时buffer里进行去重, 有主键
union all:合并不去重
group by原理:用到了temporary buffer,在这里新建一个临时表用来存储两列数据,一个是要聚合的字段名,一个是计数器count()的结果
执行计划的extra里的字段是:using temporary
group by耗费的是CPU,每次拿到值都需要在临时表里进行扫描,并且默认的会对group by的结果进行排序,若不需要默认排序则order by null即可
一张表最多4~5个索引
用到temporary buffer的操作:group by 、union 、 distinct
执行计划的阅读- 在SQL语句前加explain,调查最终查询的执行计划,这条SQL语句是不执行的,只是打印出执行计划
- 执行计划的内容:
id:SQL语句里简单标识符
select_type:描述正在执行的select类型,一般是simple
table:表名
partitions:查询将要匹配记录的分区
type:查询方式(全表扫描、索引) all:全表扫描 ref:等值索引扫描 range:索引范围扫描 index:索引扫描 符合最左前缀原则
possible:可能用到的索引都有啥
key:最终用到的索引
key_len:用到的索引所占的字节数 int(x)表示整数,永远占4字节,因为x是这个整数的位数
ref:
rows:估计要取出select的数据大概要扫描多少行
filtered:过滤率是多少,即最终得到的数据行数与扫描的行数的比值
extra:附加信息 using filesort:用了用户自身的sort buffer去排序
快速创建一张和已存在表结构数据一样的表:create table cityex as select *from city;
项目中创建一张类似的表可用该方法,然后再将不需要的字段删除,将需要的字段插入即可
analyze table 表名:手动收集统计信息
查看统计信息:show index from 表名
创建复合索引:alter table cityex add index idx_name_id(name, id);
- 任何带 函数、表达式、含不等于!或<>的列、包含null 的查询条件不能走索引
in可以走索引,not in不可以走索引
like 'h%'可以走索引,like '%h'不能走索引,最左原则 - SQL里null与任何值做计算都是null
锁保证了事务的隔离性
全局锁使用场景:做备份
命令:Flush tables with red lock
加在表上的锁:lock table、MDL锁
表锁每登录一个用户就是进入一个session会话
-- 读锁 lock table emp read; -- 解锁 unlock tables; -- 写锁,也叫排他,其他人不能做任何操作 lock table emp write; -- 解锁 unlock tables;元数据锁 MDL(metadata lock)
- 参数tx_isolation:可以设置MySQL事务的隔离级别
begin; -- 开启一个事务
-
要注意线上加索引可能会导致整个数据库锁住
-
select后边最好不要加 * ,需要什么则查询什么,这样便于索引优化
-
排序的原理:
用了排序则在extra:附加信息里有 using filesort,即使用了sort buffer,不能证明使用了磁盘排序
小文件:直接在sort buffer 里排序
大文件用了磁盘排序:先把要排序的内容从磁盘里加载到Innodb buffer pool,然后将其分成多个小部分再sort buffer里进行排序,每次排好序的临时文件数据就放到磁盘里,然后再将每一个排好序的临时文件中的最小数据再拿出来进行排序
再设计上要尽量避免大数据量的排序,可以在业务上进行区分等等
select * from cityex order by population desc; --使用filesort delect population from cityex order by population desc; --使用了索引排序 delect name, population from cityex order by population desc; --使用filesort行级锁 总结
-
change buffer 和 MRR都是Innodb特有的
-
Innodb索引的特点:
(1)最左原则
(2)二级索引要回表(即到主键索引中再查一遍)
(3)支持索引扫描 -
Innodb的索引:
聚簇索引(整张表的主键,放在叶子上面的)
唯一索引
普通索引
前缀索引(取索引字段的前几位作为索引,需要考虑前缀的区分度) -
唯一索引和普通索引的区别:
只有普通索引能用change buffer
普通索引适合于频繁写入,但不常读取的操作
对于写完即读的情况,二者没有区别 -
select * from cityex where upper(name)='BEIJING';这种操作不走索引MySQL里不走函数索引
-
复合索引扫描时符合最左前缀原则。不符合最左前缀原则时,不走索引
要定位到上面的分支才能走下面的分支,没有索引跳跃扫描 -
explain中的type:数据扫描的方式,为ref或者range即走了索引扫描
=ref时为等值索引扫描
=range为走索引扫描 -
possible key:可能用到的索引
-
key_len:用到的索引长度。若为复合索引则会只显示用到了那几个索引的长度
int(20) 长度4 id int 长度为5(空值位占1位)
cahr(20) 长度20
varchar(20) 长度22 -utf8 占20*3+2=62位
datetime 长度8
date 长度3
如果字段可为 null,则需要额外再加1 -
表连接的优化方法: Batched Key Access(BKA) >
Index NLJ(最常用) > Block NLJ(是Simpe NLJ的优化,用的join buffer)> Simple NLJ(Index NLJ中没有被驱动表则会退化为这个) -
二级索引的范围查询:用MRR(Multi Range Read)
MRR也是每次用户一登入都会给用户分配的一个内存的buffer -
Batched Key Access(BKA)与Block NLJ会用到join buffer
-
用了order by的查询会在extra:附加信息中显示 using filesort,只说明用了sort buffer,不能证明用了排序
-
为了优化sort buffer,会给order by后面加上索引,这个索引要求和select后面的内容相对应
binlog 日志:记录完整地事务;以commit的顺序来进行记录;
和redo 日志一样也在Innodb里
sync_binlog:=1,0(每秒钟落一次盘),n
参数sync_binlog与innodb_flush_log_tx_commit,都设为1是最安全的;都设为0是性能最高的
如果要求写入即读,只能采用单实例,即主库写主库读
半同步复制
-
PXC主从架构业务使用场景:
多点写入
数据同步
并发复制
快速维护
自动克隆 -
PXC分布式数据库的障碍:
无法实现即时读写,只是把日志先传递到了其他的库
乐观锁定
基于中间件的分布式
分布式:
查询和修改尽量在一个分片上
- 通过information_schema.innodb_trx视图
- Threads_running :这个数值指的是激活的连接数,这个数值一般远低于connected数值。Threads_running是代表当前并发数
- Threads_connected :跟show processlist结果相同,表示当前连接数
- 优化器
- optimze table city;
整数类型包括 TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT
浮点数类型包括 FLOAT 和 DOUBLE
定点数类型为 DECIMAL。
declare @i int set @i=1 while @i<30 begin insert into test (userid) values(@i) set @i=@i+1 end
loop
repeat
- lenngth()一个汉字占3个字节;一个字母或数字占1个字节
length(“你好”)=6
length(“he”)=2 - char_length()中无论汉字/字符还是数字都占1个字符
char_length(“你好”)=2
char_length(“he”)=2
- 不支持字符类型
- 不支持临时数据
- 不能用其他的聚合函数作为参数
- 元数据锁:MDL锁(metadata lock)
- lock table
- information_schema.innodb_lock
- show engine innodb status;



