MySQL进阶

1.储存引擎

1.MySQL体系结构

  • 客户端连接器:PHP,Python,Java的JDBC等

  • MySQL服务端:

  • 连接层:

    • 连接池,用于接收客户端的连接,完成连接的处理,认证授权(校验用户名密码),校验每个客户端的权限,相关安全方案,检查是否超过最大连接数
  • 服务层:SQL接口,完成缓存的查询,SQL的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程,函数等

    • SQL接口
    • 解析器
    • 查询优化器
    • 缓存
  • 引擎层:复杂数据的存储和提取,服务器通过API和存储引擎进行通信。其中含有多个可选择的引擎,不同的存储引擎有不同功能,也可以在此基础上扩展。

    • 可插拔存储引擎
      • InnoDB(MySQL5.5后的默认引擎)
      • NDB
      • MyISAM
  • 存储层:主要将数据存储在文件系统之上,并完成与存储引擎的交互

    • 系统文件
    • 文件和日志

2.储存引擎简介

  • 存储数据,建立索引,查询/更新数据等技术的实现方式。
  • 存储引擎是基于表的,而不是基于库的,所以存储引擎也可以称为表类型

1.查询表的存储引擎

1
show create table 表名;
  • 用查询建表语句查询
1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE `user1` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(10) NOT NULL,
`age` int DEFAULT NULL,
`status` char(1) DEFAULT '1',
`gender` char(1) DEFAULT NULL,
`id_for_connent` int DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`),
KEY `fk_user1_name_dept_id` (`id_for_connent`),
CONSTRAINT `fk_user1_name_dept_id` FOREIGN KEY (`id_for_connent`) REFERENCES `dept` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `user1_chk_1` CHECK (((`age` > 0) and (`age` <= 120)))
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用于学习约束的用户表'
  • 其中查询出的ENGINE=InnoDB 就是存储引擎,InnoDB为默认引擎

  • AUTO_INCREMENT=4 代表id是自增的

  • DEFAULT CHARSET=utf8mb4 即当前表的默认字符集是utf8mb4

  • COLLATE=utf8mb4_0900_ai_ci 即排序方式

2.创建表时指定存储引擎

1
2
3
4
5
6
7
8
9
create tables 表名(

字段1 字段1的类型(comment 字段1的注释), #注意逗号

字段2 字段2的类型(comment 字段2的注释),

........

) engine=innodb(comment 表注释);

3.查看当前数据库支持的存储引擎

1
show engine;
  • 查询后显示

engine(引擎名称) support(是否支持) comment(描述) transactions XA savepoints

3.储存引擎特点

1.InnoDB

  • InnoDB是一种兼顾高可靠性和高性能的通用存储引擎

  • 特点:

    • DML(数据的增删改)操作遵循ACID模型(事务的四大特性),支持事务
    • 行级锁 ,提高并发访问性能
    • 支持外键 foreign key约束,保证数据的完整性和正确性
  • 文件:

    • xxx.ibd:xxx代表的是表名,innodb引擎的每一张表都会对应这样一个表空间文件,储存该表的表结构(frm,sdi),数据和索引

    • #文件不能直接打开,可以在文件夹中打开cmd,ibd2sdi xxx.idb 会返回一长串json

    • 参数:innodb_file_per_table

    • #可以用

      1
      show varialbes like‘innodb_file_per_table’;

      查看是否打开,打开则表示每张表对应一个表空间文件

  • 逻辑存储结构

    • tablespace:表空间,其中包含多个segment
      • segment:段,其中包含多个extent
        • extent:区(大小固定,每个区1M),其中包含多个page(可以包含64个页)
          • page:页(大小固定,每个页16K,页也是磁盘操作的最小单元),其中包含多个row
            • row:行,其中包含最后一次操作事务的id,指针,字段

2.MyISAM

  • MyISAM是MySQL早期的默认存储引擎

  • 特点:

    • 不支持事务,不支持外键
    • 支持表锁,不支持行锁
    • 访问速度快
  • 文件:

    • xxx.MYD:存储数据
    • xxx.MYI:存储索引
    • xxx.sdi:存储表结构信息,打开是文本形式的json,json格式化后查看

3.Memory

  • Memory引擎的表数据是存储在内存中的,由于受到硬件问题,或断电问题的影响,只能将这些表作为临时表或缓存使用

  • 特点:

    • 内存存放(所以访问速度快)
    • hash索引(默认)
  • xxx.sdi:存储表结构信息

特点 InnoDB MyISAM Memory
存储限制 64TB
事务安全 支持
锁机制 行锁 表锁 表锁
B+tree索引 支持 支持 支持
Hash索引 —支持
全文索引 支持(5.6后) 支持
空间使用 N/A
内存使用 中等
批量插入速度
支持外键 支持

4.储存引擎选择

  • 应根据应用系统的特点选择合适的存储引擎,对于复杂的应用系统,可以根据实际情况选择多种存储引擎
    • InnoDB:是MySQL的默认存储引擎,支持事务,外键。如果应用对事务的完整性有较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新,删除操作,那么InnoDB存储引擎是比较合适的选择
    • MyISAM:如果应用是以读写操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性,并发性要求不是很高,那么MyISAM是合适的选择
    • Memory:将所有数据保存在内存种,访问速度快,通常用于临时表及缓存。Memory的缺陷就是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性

2.索引

1.索引概述

  • 索引(index):帮助MySQL高效获取数据数据结构(有序) 。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据上实现高级查找算法,这种数据结构就是索引。

# 无索引时,例如

1
select * from user where age = 40

会与每一条数据匹配,称为全表扫描,性能极低

  • 优点:

    • 提高数据检索的效率,降低数据的IO成本
    • 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗
  • 缺点(影响较小)

    • 索引列也要占用空间
    • 索引提高了查询效率,但同时也降低更新表的速度,如对表进行insert,update,delete时效率降低

2.索引结构

  • MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的索引结构
索引结构 描述
B+Tree索引 最常见的索引类型,大部分引擎都支持B+树索引
Hash索引 底层数据结构是用哈希表实现的,只有精确匹配索引列查询才有效,不支持范围查询
R-tree(空间索引) 空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间的数据类型,使用较少
Full-text(全文索引) 是一种通过建立倒排索引,快速匹配文档的方式。类似于Lucene,Solr,ES
索引 InnoDB MyISAM Memory
B+Tree索引 支持 支持 支持
Hash索引 不支持 不支持 支持
R-tree索引 不支持 支持 不支持
Full-text索引 5.6后支持 支持 不支持

1.二叉树

  • 树状图,但是每个节点下最多只有两个分叉

  • 缺点:

    • 1.极端情况如顺序插入时,会形成一个链表(即斜树,只有左子树或者只有右子树),查询性能大大降低。
    • 2.大数据量情况下,层级较深,检索速度慢
  • 红黑数只能解决第一个缺点

2.B-Tree(多路平衡查找树)

  • 同样是树状图,但是可以有多个节点,一个节点的子节点个数称为度数(max-degree),每个节点可以存储(度数-1)个key,度数个指针
  • 当插入超过(度数-1)个key时,中间元素则向上裂变,例如数字:23,56,28,49,如果再插入一个数字59,则中间元素49向上裂变,(23,28),(56,59)成为49的两个子节点

3.B+Tree

  • 与B树类似,但是所有元素都会出现在叶子节点,上面的非叶子节点主要起到索引的作用,叶子节点则存放数据。同时叶子节点形成一个单向链表,每个节点通过指针指向下一个元素
  • 在MySQL中对B+Tree进行了优化,增加了一个指向相邻页子节点的链表指针,形成带有顺序指针的B+Tree,提高区间访问的性能
  • 其中的键值,数据,指针,都是存放在页

# 假设一行数据大小为1k,那高度为2时可以存储18736字节的数据,高度为3时可以存储21939856字节的数据

4.Hash

  • 哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中

  • 如果两个或多个键值,映射到同一个槽位上,就产生了hash冲突(hash碰撞),可以通过链表解决

  • 特点

    • 1.Hash索引只能用于对等比较(=,in),不支持范围查询(between,>,<,…)
    • 2.无法利用索引完成排序操作
    • 3.查询效率高,通常(不出现hash碰撞)只需要一次检索就可以,效率通常要高于B+tree索引
  • 存储引擎支持

    • MySQL中,支持hash索引的是Memory引擎,而InnoDB中具有自适应hash功能,hash索引是存储引擎根据B+Tree索引在指定条件下自动构建的

5.思考

  • 为什么InnoDB存储引擎用B+Tree索引结构
    • 相对于二叉树,层级少,搜素效率高
    • 对于B-tree,无论叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低
    • 相对Hash索引,B+tree支持范围匹配及排序操作

3.索引分类

分类 含义 特点 关键字
主键索引 针对于表中主键创建的索引 默认自动创建,只能有一个 primary
唯一索引 避免同一个表中某数据列中的值重复 可以有多个 unique
常规索引 定位特定数据 可以有多个
全文索引 查找的是文本中的关键词,而不是比较索引中的值 可以有多个 fulltext
  • 在InnoDB中根据索引的存储形式,又分为两种
分类 含义 特点
聚焦索引 将数据存储与索引放在一块,索引结构的叶子节点保存了行数据 必须有,且只有一个
# 叶子节点下对应的是主键和这一行的行数据
二级索引 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 可以存在多个
# 叶子节点下对应的是索引对应字段和字段对应的主键
  • 两个索引都是B+tree索引

  • 例如

    1
    select * from user where name='arm';

    这时候就会先根据name这个字段的二级索引查找对应name下面对应的主键,再根据主键去主键的聚焦索引查找对应主键下面的行数据。这个过程称为回表查询

  • 所以根据主键查询的效率要更高

  • 聚焦索引选取规则

    • 如果存在主键,主键索引就是聚焦索引
    • 如果不存在主键,将使用第一个唯一索引作为聚焦索引
    • 如果以上都没有则自动生成一个rowid作为隐藏的聚焦索引

4.索引语法

1.创建索引

1
create unique/fulltext index 索引名称 on 表名(表中字段名1,.....);

# 如果省略unique或者fulltext则创建一个常规索引

# 关联一个字段则为单列索引,多个则为联合索引(联合索引)

# 索引名称规范一般为idx_表名_字段名

2.查看索引

1
show index from 表名\G;

# 不加G显示出来的是表格,但是内容过多会导致表格变形,加上G可以使其每行显示一条

3.删除索引

1
drop index 索引名称 on 表名;

5.SQL性能分析

1.SQL执行频率

  • MySQL中通过show session/global status 可以提供服务器状态信息。

# session和global分别为当前会话和全局

1
show global status like ’Com_____________';

# Com后面有几个下划线则为模糊匹配几个字符,可以查看当前数据库的insert,update,delete,select的访问频次

2.慢查询日志

  • 慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志。MySQL的慢查询日志默认不开启,需要在MySQL的配置文件(/etc/my.cnf)中配置

  • slow_query_log=1 在配置文件中配置开启

  • long_query_time=2 设置慢查询日志的时间为2秒

1
show variables like ‘slow_query_log';

查询慢查询日志是否打开

  • 日志存放在**/var/lib/mysql** 中会有一个以**-slow.log** 结尾的文件

# 日志中会记录:哪一个用户通过哪一个主机连接,执行用了多少时间,锁了多少行,返回了多少条记录,用的哪一个数据库,执行的时间,执行的语句

  • tail -f 文件名 在Linux中这个指令可以查看文件实时输出的内容

3.profile详情

1
select @@have_profiling

# 通过have_profiling参数,能够看到当前MySQL是否支持profile操作

1
select @@profiling

# 查看返回数字,是否开启

1
set profiling=1

# 默认profiling是关闭的,可以选择在session/global级别开启

1
show profiles;

#查看每一条SQL的耗时基本情况

# 会显示query_id,duration,query

1
show profile for query 指定query_id;

# 查看指定query_id的SQL语句各阶段的耗时情况

1
show profile cpu for query 指定query_id;

# 查看指定query_id的SQL语句cpu的使用情况

4.explain执行计划

  • explain或者desc命令获取MySQL如何执行select语句的信息,包括select语句执行过程中表如何连接和连接的顺序
1
explain select 字段列表 from 表名 where 条件;

# 直接在select语句前加关键字explain/desc

# 会显示id,select_type(查询类型),table(使用的表),partitions(分区),type(连接的类型),possible_keys(可能用到的索引),key(实际用到的索引),key_len(索引长度),rows(扫描记录数),ref,filtered,extra

  • explain执行计划各字段含义

    • id

      • select查询的序列号,表示查询中执行select子句或者是操作表的顺序(id相同,执行顺序从上到下;id不同,则值越大的先执行)

        # 多表查询会出现多条记录,会有多个id

    • select_type

      • 表示select的类型,常见有simple(简单表,即不使用表连接或者子查询),primary(主查询,即外层的查询),union(union中的第二个或者后面的查询语句),subquery(select/where之后包含了子查询)等
    • type

      • 表示连接(访问)类型,性能由好到坏为,null(基本不会出现,除非不访问表),system(访问系统表),const(根据主键和唯一索引访问),eq_ref(联表查询中根据主键和唯一索引访问),ref(根据非唯一性的索引访问),range,index(遍历整个索引),all(全表扫描)
    • possible_keys

      • 表可能会用到的索引,会有一个或多个
    • key

      • 实际使用的索引,没有使用索引则为null
    • key_len

      • 表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好
    • rows

      • MySQL认为必须要执行查询的行数,在innodb引擎的表中,是估计值
    • filtered

      • 表示返回结果的行数占需要读取行数的百分比,值越大越好

6.索引使用

1.验证索引效率

  • 在未建立索引之前,执行SQL语句查看SQL耗时
  • 针对字段创建索引后再执行相同的SQL语句,再次查看SQL耗时

2.使用原则

1.最左前缀法则
  • 最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列,如果跳过某一列,索引将部分失效,则此列后面的字段失效

  • 如果索引了多列(即联合索引),要遵守最左前缀法则

# 例如建立联合索引按顺序关联name,age,status三个字段,在使用联合查询的时候,

1
select * from 表名 where ......;

​ 要遵守最左前缀法则,则where后面必须存在name=(位置不限),否则不会根据索引查询,

​ 在查询时用and连接字段,如果跳过了age字段,则name根据索引查询,status则不会

2.范围查询
  • 联合索引中,出现范围查询(>,<),范围查询右侧的列索引失效

# 同样是上面的例子,如果查询的时候where后面age>30,则其右侧即status失效(age仍然根据索引查询)

# 在允许的情况下用>=和<=可以规避这个问题

3.索引列运算
  • 不要在索引列上进行运算操作,索引将失效

# 例如建立了关于phone这个字段的索引,

1
select * from 表名 where phone=‘号码’;

这样查询是可以根据索引查询的

如果使用

1
select * from 表名 where substring(phone,102=12’;

即用了substring函数进行了运算

则进行的是全表扫描,不会根据索引查询

# 最左前缀法则中中间字段进行了运算会导致此字段和其后的字段索引失效

4.字符串不加引号
  • 字符串类型字段使用时,不叫引号,索引将失效 ,根本原因是对其进行了隐式类型转换,字符串转为数字,而索引存储的是原字符串

# possibilities_keys为字段相关的索引,但是实际索引key为null

# 在最左前缀法则中,如果中间字段没有加引号,则此字段以及后面的索引全部失效

5.模糊查询
  • 如果仅仅是尾部查询模糊匹配(abc%),索引不会失效。但是只要头部模糊匹配(%abc),索引就失效

# 同样在最左前缀法则中尾部模糊查询会导致当前索引可以使用,其后索引失效。

# 只要有头部模糊就会导致,此字段索引及其后索引失效

6.or连接的条件
  • 用or分割开的条件,任一条件中列没有索引,那么前后索引都不会被用到

# 只要有条件中的字段是联合索引里的中间字段且没有单独索引则失效,如果只是最左索引则可以使用

# 会有可能索引,但实际索引key为null

# 如果要查询字段是联合索引里的中间字段则需要创建单独索引

7.数据分布影响
  • 如果MySQL评估使用索引比全表更慢,则不使用索引

# 主要取决于数据的分布,如果条件筛选出来的是小部分数据则会使用索引,反之不使用

8.SQL提示
  • 在SQL语句中加入一些提示来达到优化操作的目的
1.use index:建议MySQL使用某个索引,实际使用的索引根据MySQL的判断
1
select * from 表名 use index(索引名)where ......;

# 可以用explain查看最终使用了哪个索引

2.ignore index:不使用某个索引
1
select * from 表名 ignore index(索引名)where ........;
3.force index:强制使用某个索引
1
select * from 表名 force index(索引名)where .........;
9.覆盖索引
  • 尽量使用覆盖索引(查询使用了索引,并且需要返回的列在该索引中已经全部能够找到),减少select*出现

# 例如建立了关联了phone,age,name的联合索引,select id,phone,age,name后面条件包含三个字段

​ 根据二级索引,主键id也包含在内,四个字段都在使用的索引中,这时候用explain查询,extra信息会显示

​ using where;using index:查找使用了索引,但是需要的数据都在索引列中能找到,使用不需要回表查询

​ 如果再查询status,这个字段不包含在使用到的索引中,则extra信息会显示

​ using index condition:查找使用了索引,但是需要回表查询数据

​ 如果要查询则再建立phone,age,name,status的联合索引,以提高效率,单独再建一个status的单列索引仍然会回表查询

10.前缀索引
  • 当字段类型为字符串(varchar,text等)使,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘io,影响查询效率。此时可以只将字符串的一部分前缀建立索引,这样可以对的节约索引空间,从而提高索引效率

  • 语法:

1
create index 索引名称 on 表名(字段名(n));

# n表示要提取这个字段的前面n个字符来构建索引

  • 前缀长度
  • 可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的
  • 计算公式:
1
select count(distinct 字段名)/count(\*from 表名;

# distinct用于去除重复记录

1
select count(distinct substring(字段名,起始位置,截取长度))/count(\*from 表名;

# substring用于截取,起始位置从1开始

  • 前缀索引查询流程
1
select * from 表名 where email=‘完整的邮箱’;

# 先根据完整邮箱的前n位到创建的邮箱的前n位的二级索引中查找得到对应主键,再到聚合索引中找到对应主键的行数据,再将行数据中的email与要查询的完整邮箱比对,完全一致则返回数据。如果出现前n位相同,则将几个主键都拿到聚合索引中查询,最终一致的再返回出来

11.单列索引与联合索引
  • 如果存在多个查询条件,考虑针对于查询字段建立索引,建议建立联合索引,而非单列索引

# 在使用时,即使创建了联合索引,MySQL可能仍会使用单列索引,会回表查询,可以使用SQL提示

  • 联合索引情况:
  • 仍然是B+tree,每个节点存储的键值是几个索引的组合情况

# 例如将phone,name,age按顺序关联成联合索引,则每个键值的形式都是phone+name+age,根据phone来排序,phone一致则再根据name来排序,以此类推。同时phone就作为最左列

7.索引设计原则

1.针对于数据量较大(数据量超过100万左右),且查询比较频繁的表建立索引。

2.针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引。

3.尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。

4.如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。

5.尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。

6.要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。

7.如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询。

3.SQL优化

1.插入数据

1.insert优化

1.批量插入
1
insert into 表名 values(),(),(),.....;

# 因为每一次insert插入都会与数据库进行建立连接和网络传输,所以将多个insert合并为一个

# 插入数据量在500-1000较为合适

2.手动提交事务
1
2
3
4
5
6
7
8
9
10
start transaction;

insert......;

insert......;

insert......;

commit;

3.主键顺序插入
  • 主键乱序插入
  • 主键顺序插入

# 顺序插入的性能高于乱序插入,与MySQL的数据组织结构有关

2.大批量插入数据

  • 如果一次性需要插入大批量数据,使用insert语句插入性能较低,此时可以使用MySQL数据库提供的load指令进行插入

  • 客户端连接服务端时,加上参数 --local-infile 用于加载本地文件

1
mysql --local-infile -u root -p
  • 设置全局参数local_infile为1,开启从本地加载文件导入数据的开关
1
set global local_infile=1

# 可以再用

1
select @@local_infile

查看值为多少,是否开启

  • 执行load指令将准备好的数据,加载到表结构中
1
load data local infile ‘/root/文件名’ into ‘表名’ fields terminated by  ‘,’ lines terminated by ‘\n';

# ‘,’表示指定用,分割每个字段,‘\n’表示指定用\n分割每一行数据

# 需要先将文件上传到服务器中

# 同样要按照主键顺序插入

2.主键优化

1.数据组织方式

  • 在innodb存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(iot)

  • 页分裂

  • 页可以为空,也可以填充一半,或者全部填充,每个页至少包含2行数据(如果某一行数据过大,会行溢出),根据主键排列

  • 主键顺序插入:当第一个页填充满了,数据会在第二个页继续填充,同时会在一二页中维护一个双向指针

  • 主键乱序插入:例如第一个页中有值为1 5 9 23 47,第二个页中有值为55 67 89 101 107,且这两页已经填充满,此时插入一个值为50的行数据,那么会先生成一个新的数据页,再找到第一个页的50%的位置,即1 5 9和23 47,则先将23 47移动到生成的第三个页,再将50插入到第三个页,那么原本的链表指针是一二之间的,则重新设置一三间的双向指针和三二之间的双向指针,此现象被称为页分裂

  • 页合并

  • 当删除一行数据时,并没有真正的删除,只是数据被标记为删除并且它的空间允许被其他数据声明使用

  • 当页中删除的数据达到merge_threshold(合并页的阈值,默认为页的50%,可以自己设置,在创建表或者创建索引时指定),innodb会开始寻找最近的页查看是否可以将两个页合并以优化空间使用

2.主键设计原则

  • 满足业务需求的情况下,尽可能降低主键的长度,过长会占用空间,耗费大量磁盘io

  • 插入数据时,尽量选择顺序插入,选择auto_increment自增主键

  • 尽量不要使用uuid(每次生成的是无序的,导致乱序插入,可能页分裂,uuid v7是有序的)做主键或者其他自然主键,如身份证号(长度较长)

  • 业务操作时,避免对主键的修改

3.order by优化

  • using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort buffer中完成排序操作,所以不是通过索引直接返回排序结果的排序都叫FileSort排序

  • using index:通过有序索引顺序扫描直接返回有序数据,这种情况即为using index,不需要额外排序,操作效率高

  • 索引创建出来默认是升序排序的,在信息collation中显示A(asc),所以如果在查询时order by后面的字段都是倒序,就会在explain的extra中提示backward index scan(反向扫描索引)

  • 如果既有升序又有降序那么在extra中就会既有using index又有using filesort

# 可以在创建索引的时候在字段名后加asc/desc来指定升序或降序

  • 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则
  • 尽量使用覆盖索引
  • 多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则
  • 如果不可避免的出现filesort,大数据量排序时,可以适当增大排序缓冲区大小sort_buffer_size(默认256k)

# 可以用

1
show variables like ‘sort_buffer_size’;

来查看排序缓冲区的大小

# 如果排序缓冲区满了,会在磁盘文件中排序,性能就会较低

4.group by优化

  • 通过索引来提升效率
  • 分组操作时,索引的使用也是满足最左前缀法则的

# 在group by后面使用字段也可以走联合索引

5.limit优化

  • 正常limit查询时,如从200000000条开始一页10行数据,这时会将前200000010都排序,但是最终只返回10条记录
  • 可以通过覆盖索引和子查询来解决

6.count优化

  • InnoDB中查询count(*)会很耗时,执行时需要把每一行数据从引擎里读出来,然后累加
  • erMyISAM引擎把一个表的总行数存在了磁盘上,因此执行count(*)的时候会直接返回这个数,效率高

# 前提是查询时后面没有where条件

  • count的几种用法

    • count()是一个聚合函数,对于返回的结果集,一行一行地判断,如果count函数的参数不是null,累计值就加1,否则不加,最后返回累计值

    • 用法:

      • count(*):InnoDB引擎不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行累加
      • count(主键):InnoDB引擎会遍历整张表,把每一行的主键id值都取出来,返回给服务层,服务层获取主键后直接按行进行累加(主键不可能是null)
      • count(字段):
        • 没有not null约束:InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null则计数
        • 有not null约束:InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回服务层直接累加
      • count(1):InnoDB引擎会遍历整张表,但不取值,服务层对于返回的每一行,放一个数字1进去,直接按行累加

      # 官方文档中说,count(*)和count(1)的优化是一样的

      # 效率:count(字段)<count(主键)<count(1)=count(*)

7.update优化

  • InnoDB的行锁是针对索引加的锁,不是针对记录加的,并且该索引不能失效,否则会从行锁升级为表锁

# 在一个窗口中开启事务,where条件后跟的是有索引的且不能是前面提到过的各种索引失效的字段,则符合条件的行数据会被锁定(在另一个会话中可以对其他行数据进行update),否则则会将整张表锁住,在另一个会话中开启事务对表中任意一行数据update都无法成功

4.视图

1.介绍

  • 视图(view)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视图查询中使用的表(基表/基础表),并且在使用视图时动态生成的
  • 视图只保存查询的SQL逻辑,不保存查询结果,所以在创建视图的时候,主要在于创建SQL查询语句

2.基本语法

1.创建

1
create or replace view 视图名称(列名列表) as select语句 wtih cascaded/loacl check option;

# 如不需要替换视图则or replace可以省略

# 不需要检查增删改是否满足条件时wtih cascaded/loacl check option可以省略

# 例如

1
create view stu_i_n as select id,name from student where id <=10;

2.查询

  • 查看创建视图的语句
1
show create view 视图名称;
  • 查看视图数据
1
select * from 视图名称 .........;

# 后面可以添加条件

3.修改

1
create or replace view 视图名称(列名列表) as select语句 wtih cascaded/loacl check option;

1
alter view 视图名称(列名列表) as select语句 wtih cascaded/loacl check option;

4.删除

1
drop view if exists 视图名称 1,视图名称2, .......;

3.检查选项

  • 当使用with check option子句创建视图时,MySQL会通过视图检查正在更改的每个行,例如插入,更新,删除,以使其符合视图的条件。MySQL允许基于另一个视图创建视图,它还会检查所依赖视图 中的规则以保持一致性。为了确定检查的范围,MySQL提供了两个选项:cascaded和local,默认值为cascaded(级联)

  • cascaded :检查视图以及其所依赖的视图的条件

  • local :检查视图条件,再查看其所依赖的视图是否有with check option来检查条件,有则检查,没有则只需满足当前视图条件

4.更新

  • 要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。如果视图包含以下任何一项,则视图不可更新
    • 聚合函数或窗口函数(sum,min,max,count)
    • distinct
    • group by
    • having
    • union或union all

5.作用

  • 简单
    • 视图不仅可以简化用户对数据的理解,也可以简化操作。经常使用的查询可以被定义为视图(类似于封装成函数),从而使得用户不必为以后的操作每次指定全部的条件
  • 安全
    • 数据库可以授权,但不能授权到数据库的特定行和列上。通过视图用户只能查询修改授权的数据
  • 数据独立
    • 视图可以帮助用户屏蔽真实表结构变化带来的影响,当基表的字段名改变时,可以通过起别名的方式使视图的字段名和基表原始字段相同

5.储存过程

1.介绍

  • 存储过程是事先经过编译并存储在数据库中的一段SQL语句集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器间的传输,提高数据处理的效率

  • 存储过程思想上很简单,就是数据库SQL语言层面的代码封装与重用

  • 特点:

    • 封装,重用
    • 可以接收参数,也可以返回数据
    • 减少网络交互,提升效率

2.基本语法

1.创建

1
2
3
4
5
6
7
create procedure 存储过程名称(参数列表)

begin

SQL语句

end

# 在命令行 中创建存储过程时,SQL语句中会有**;** ,导致判定为SQL语句完成

# 需要用关键字delimiter指定SQL语句的结束符

  • delimiter 指定符号

  • 在end后将;改为指定符号

  • 同样也可以改回;

2.调用

  • call 名称(参数);

3.查看

1
select * from information_schema.routines where routine_schema = 'xxxx'

# 在information_schema这个数据库中的routines这张表中,routine_schema限制数据库名称

# 查询指定数据库的存储过程及状态信息

1
show create procedure 存储过程名称;
  • # 查询某个存储过程的定义

4.删除

1
drop procedure if exists 存储过程名称;

3.变量

1.系统变量

  • 是MySQL服务器提供,不是用户自定义的,属于服务器层面,分为全局变量(global),会话变量(session)

# 没有指定session或global则默认为session

1.查看系统变量
1
show session/global variables;

# 查看所有系统变量

1
show session/global variables like ‘.........’;

# 通过like模糊匹配查找变量

1
select @@session/global.系统变量名;

# 查看指定变量的值

2.设置系统变量
1
set session/global 系统变量名=值;
1
set @@session/global 系统变量名=值;

# 重启后设置的全局参数会恢复默认值,可以在/etc/my.cnf中配置

2.用户自定义变量

  • 是用户根据需要自己定义的变量,用户变量不用提前声明 ,在用的时候直接用**@变量名** 使用就可以,其作用域为当前连接
1.赋值
1
set @变量名1=1,......;
1
set @变量名1=1,......;

# 建议使用**:=**

1
select @变量名1=1,......;
1
select 字段名 into @变量名 from 表名;

# 将查询结果赋给变量

2.使用
1
select @变量名1,......;

# 直接使用一个未赋值的变量也不会报错,只会返回null

3.局部变量

  • 是根据需要定义的在局部生效的变量,访问前需要declare声明 ,可用作存储过程内的局部变量和输入参数,局部变量的范围是在其内声明的begin…end块
1.声明
1
declare 变量名 变量类型 default ....;
2.赋值
1
set 变量名=值;
1
set 变量名 :=值;
1
select 字段名 into 变量名 from 表名 ......;

4.if

  • 语法
1
2
3
4
5
6
7
if 条件1 then
.......
elseif 条件2 then
........
else
........
end if;

# 例如

1
2
3
4
5
6
7
8
9
10
11
12
13
create procedure p1()
begin
declare score int default 58 #这里将分数的默认值设为58,没有参数
declare result varchar(10);
if score >= 85 then
set result :='优秀';
elseif score >= 60 then
set result :='及格';
else
set result :='不及格'
end if;
select result;
end;

5.参数

类型 含义 备注
in 该类参数作为输入,也就是需要调用时传入值 # 默认为in
out 该类参数作为输出,也就是该参数可以作为返回值
inout 既可以作为输入参数,也可以作为输出参数 # 可以用于作为百分制转化的参数
  • 语法
1
2
3
4
5
6
7
create procedure 存储过程名称(in/out/inout 参数名 参数类型)

begin

SQL语句

end

# 例如

1
2
3
4
5
6
7
8
9
10
create procedure p1(in score intout result varchar(10)) #这里将分数的类型设置为in传入,同时设置resultout返回
begin
if score >= 85 then
set result :='优秀';
elseif score >= 60 then
set result :='及格';
else
set result :='不及格'
end if;
end;

# call的时候就要传入一个参数,同时还要自定义一个变量来接收返回的结果

1
call p1(68,@result1);

# 这里执行完不会直接显示result,要查询@result1才能显示

6.case

  • 语法一
1
2
3
4
5
6
case 表达式
when1 then sql1语句
when2 then sql2语句
......
else sql3语句
end case;
  • 语法二
1
2
3
4
5
6
case
when 条件表达式1 then sql1语句
when 条件表达式2 then sql2语句
.......
else sql3语句
end case;

# 例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
create procedure p6(in month int)
begin
declare result varchar(10);
case
when month >= 1 and month <= 3 then
set result :='第一季度'
when month >= 4 and month <=6 then
set result :='第二季度'
when month >= 7 and month <=9 then
set result :='第三季度'
when month >= 10 and month <= 12 then
set result :='第四季度'
else
set result :='非法参数'
end case
select concat('您输入的月份为:',month',所属的季度为:'result);
end;

7.循环

1.while

  • while循环是有条件的循环控制语句。满足条件后,再执行循环体中的SQL语句

  • 语法

1
2
3
while 条件 do
sql语句
end while;

# 先判定条件,如果为true,则执行语句,否则不执行

# 例如从1到n的累加

2.repeat

  • repeat是有条件的循环控制语句,当满足条件时退出循环

  • 语法

1
2
3
4
repeat
sql语句
until 条件
end repeat;

# 先执行一次语句,然后判定是否满足条件,满足则退出,不满足则继续下一次循环

# 同样也可以做累加的操作

3.loop

  • loop实现简单的循环,如果不在SQL语句中增加退出循环的条件,可以用其来实现简单的死循环

  • loop配合以下两个语句使用

  • leave :配合循环使用,退出循环

  • iterate :必须用在循环中,作用时跳过当前循环剩下的语句,直接进入下一次循环

  • 语法

1
2
3
标识名:loop
sql语句
end loop 标识名;
1
leave 标识名;
1
iterate 标识名;

# 同样可以做累加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
create procedure p9(in n int)
begin
declare total int default 0;

sum:loop
if n<=0 then
Leave sum;
end if;

set total:= total+n;
set n:=n-1;
end loop sum;

select total;
end;

# 配合iterate可以做到累加偶数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
create procedure p9(in n int)
begin
declare total int default 0;

sum:loop
if n<=0 then
Leave sum;
end if;

if n%2 = 1 then
set n := n-1;
iterate sum;
end if;

set total:= total+n;
set n:=n-1;
end loop sum;

select total;
end;

8.游标(光标)

  • 游标(cursor)是用来存储查询结果集的数据类型,在存储过程和函数中可以使用游标对结果集进行循环处理。游标的使用包括游标的声明,open,fetch,close

  • 语法

  • 声明游标

1
declare 游标名称 cursor for 查询语句;

# 游标的声明要在普通变量声明的后面

  • 打开游标
1
open 游标名称;
  • 获取游标记录
1
fetch 游标名称 into 变量1,变量2,......;
  • 关闭游标
1
close 游标名称;

9.条件处理程序

  • 条件处理程序(handler)可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤

  • 语法

1
declare handler_action handler for condition_value,condition_value,.... sql语句;

# handler_action:

​ continue:继续执行当前程序

​ exit:终止执行当前程序

# condition_value

​ sqlstate:状态码,如0200

​ sqlwarning:所有以01开头的sqlstate代码的简称

​ not found:所有以02开头的sqlstate代码的简称

​ sqlexception:所有没有被sqlwarning和not found捕获的sqlstate代码的简称

# 例如要查询所有年龄小于uage的用户的名称和专业

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
create procedure p11(in uage int
begin
declare uname varchar(100);
declare upro varchar(100);
declare u_cursor cursor for select name,profession from tb_user where age <= uage;
#游标在普通声明后声明
declare exit handler for SQLSTATE '02000' close u_cursor;
#条件处理程序,用于在循环体里结束循环

drop table if exists tb_user_pro;
create table if not exists tb_user_pro(
id int primary key auto_increment,
name varchar(100),
profession varchar(100
);
#创建空的表来接收查询出来的数据

open u_cursor;
while true do
fetch ucursor into uname,upro;
insert into tb_user_pro values (null, uname, upro);
end while;
close u_cursor;
#将游标中的数据循环读出,当读完所有数据时仍会循环读取,就会报错02000,上面的条件处理程序就会使其停止执行

end;

6.存储函数

  • 存储函数是有返回值的存储过程,存储函数的参数只能是in类型的

  • 语法

1
2
3
4
5
6
create function 存储函数名称(参数列表)
returns 数据类型 characteristic
begin
sql语句
return.......;
end;

# characteristic:

​ deterministic:相同的输入参数总是产生相同的结果

​ no sql:不包含sql语句

​ reads sql data:包含读取数据的语句,但不包含写入数据的语句

# 二进制制在版本8是默认开启的,所以会强制指定一个characteristic

# 因为有return…;所以可以直接查询这个存储函数来获取结果

# 使用较少,存储过程可以替代存储函数

7.触发器

1.介绍

  • 触发器是与表有关的数据库对象,值在insert/update/delete之前或之后,触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性,日志记录,数据校验等操作
  • 使用别名old和new来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还只支持行级触发,不支持语句级触发
触发器类型 new和old
insert型触发器 new表示将要或者已经新增的数据
update型触发器 old表示修改之前的数据,new表示将要或者已经修改后的数据
delete型触发器 old表示将要或者已经修改的数据

2.语法

  • 创建
1
2
3
4
5
6
create trigger 触发器名称
before/after insert/update/delete
on 表名 for each row #行级触发器
begin
具体逻辑;
end;
  • 查看
1
show triggers;
  • 删除
1
drop trigger 数据库名称.触发器名称

# 如果没有指定数据库名称则默认为当前的数据库

#例如一个用户表在增删改时要将增删改的内容记录到一个日志中

1
2
3
4
5
6
7
8
create table user_logs(
id int(11) not null auto_increment,
operation varchar(20) not null comment '操作类型,insert/update/delete',
operate_time datetime not null comment '操作时间',
operate_id int(11) not null comment '操作的ID',
operate_params varchar(500) comment '操作参数',
primary key(`id`)
engine=innodb default charset=utf8;

# 先创建一个日志用于记录

# 创建插入数据触发器

1
2
3
4
5
6
7
8
9
10
11
12
create trigger tb_user_insert trigger
after insert on tb_user for each row
#插入数据触发必然是after
begin
insert into user_logs(id, operation, operate_time, operate_id, operate_params) VALUES
(null, 'insert', now(), new.id, concat('插入的数据内容为:id=',new.id,',name=',new.name,',phone=',NEW.phpne,',email=',NEW.email,',professiom=',NEW.profession))
#id是自增的
#insert表示数据操作的类型
#now()获取当前数据操作时的实际
#new.id获取操作数据的id
#concat用于拼接后面这些新的数据
end

# 更新和删除也类似,更新时要同时记录更新前和更新后的数据,就再用old.字段名来获取,删除则只记录删除操作前的数据,只需要old.字段名

8.锁

1.介绍

  • 锁是计算机协调多个进程或者线程并发访问某一资源的机制。在数据库中,除传统的计算资源(cpu,ram,i/o)的争用以外,数据也是一种供许多用户享用的资源。任何保证数据并发访问的一致性,有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素

2.全局锁

  • 全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态 ,后续的dml语句,ddl语句和已经更新操作的事务提交语句都会被阻塞
  • 典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性

1.语法

  • 加全局锁
1
flush tables with read lock;
  • 备份时可以使用MySQL的工具mysqldump(不要在MySQL中执行,这是MySQL的工具)
1
mysqldump -uroot -p密码 表名>要转移到的表的表名.sql
  • 解锁
1
unlock tables;

2.特点

  • 如果在主库上备份,那么在备份期间都不能执行更新,业务停摆
  • 如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟

# 在InnoDB引擎中,可以在备份时加上参数 --single-transaction参数来完成不加锁的一致性数据备份

1
mysqldump --single-transaction -uroot -p密码 表名>要转移到的表的表名.sql

3.表级锁

  • 表级锁,每次操作锁住整张表。锁定粒度大,发生锁冲突的概率高,并发度最低。应用在MyISAM,InnoDB,BDB等存储引擎中

1.表锁

  • 可以分为两类:

    • 1.表共享读锁(read lock,读锁):所有客户端都只能读取数据,加了表锁的客户端写入会报错,其他客户端会被阻塞
    • 2.表独占写锁(write lock,写锁):加了表锁的客户端可以读写,其他客户端读写会被阻塞
  • 语法:

  • 加锁

1
lock tables 表名..... read/write;

# 可以锁定多张表

  • 释放锁
1
unlock tables;

# 或者直接将客户端关闭

2.元数据锁(meta data lock,mdl)

  • MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。MDL锁主要作用是维护元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作,为了避免DML和DDL冲突,保证读写的正确性
  • MySQL5.5中引入的MDL,当对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构进行变更操作的时候,加MDL写锁(排他)
对应SQL 锁类型 说明
lock tables xxx read/write shared_read_only/shared_no_read_write
select,select…lock in share mode shared_read(共享) 与shared_read,shared_write兼容,与exclusive互斥
insert,update,delete,select…for update shared_write(共享) 与shared_read,shared_write兼容,与exclusive互斥
alter table… exclusive(排他) 与其他的MDL都互斥
  • 查看元数据锁
1
select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks;

# metadata_locks这张表中记录了当前数据库中是元数据锁

3.意向锁

  • 为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查

  • 分为两种:

    • 意向共享锁(IS):由语句select … lock in share mode添加
      • 与表锁共享锁(read)兼容,与表锁排他锁(write)互斥,会被阻塞
    • 意向排他锁(IX):由insert,upadte,delete,select … for update 添加
      • 与表锁共享锁(read)及排他锁(write)都互斥。意向锁之间不会互斥
  • 查看意向锁及行锁的加锁情况

1
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;

4.行级锁

1.介绍

  • 行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在InnoDB存储引擎中
  • InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,不是对记录加锁。
  • 主要分为三类:
    • 行锁(record lock,记录锁):锁定单个行数据的锁,防止其他事务对此行进行update和delete。在rc(read commit),rr(repeatable read)隔离级别下都支持
    • 间隙锁(gap lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在rr隔离级别下都支持
    • 临建锁(next-key lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙gap。在rr隔离级别下支持

2.行锁

  • InnoDB实现了以下两种类型的行锁
    • 共享锁(s):允许一个事务去读一行,即共享锁之间兼容,阻止其他事务获得相同数据集的排他锁
    • 排他锁(x):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁
请求锁类型 s(共享锁) x(排他锁)
当前锁类型:s(共享锁) 兼容 冲突
x(排他锁) 冲突 冲突
SQL 行锁类型 说明
insert 排他锁 自动加锁
update 排他锁 自动加锁
delete 排他锁 自动加锁
select 不加任何锁
select…lock in share mode 共享锁 需要手动在select后加lock in share mode
select…for update 排他锁 需要手动在select后加for update
  • 在默认情况下,InnoDB在repeatable read事务隔离级别运行,InnoDB使用next-key锁进行搜素和索引扫描,以防止幻读

  • 1.针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁

  • 2.InnoDB的行锁是针对于索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,此时就会升级为表锁

  • 查看意向锁及行锁的加锁情况

1
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;

3.间隙锁/临建锁

  • 默认情况下,InnoDB在repeatable read事务隔离级别运行,InnoDB使用next-key锁进行搜素和索引扫描,以防止幻读
  • 1.索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁
  • 2.索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询需求时,next-key lock退化为间隙锁
  • 3.索引上的范围查询(唯一索引),会访问到不满足条件的第一个值为止

# 间隙锁唯一目的是防止其他事务插入间隙。间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一间隙采用间隙锁

9.InnoDB引擎

1.逻辑存储结构

  • tablespace:表空间(ibd文件)一个mysql实例可以对应多个表空间,用于存储记录,索引等数据
    • segment:段,分为数据段(leaf node segment),索引段(non-leaf node segment),回滚段(rollback segment),InnoDB是索引组织表,数据段就是B+tree的叶子节点,索引是B+tree的非叶子节点。段用来管理多个区
      • extent:区,表空间的单元结构,每个页的大小为1M。默认情况下,InnoDB存储引擎页大小为16K,即一个区中一共有64个连续的页
        • page:页,是InnoDB存储引擎磁盘管理的最小单元,每个页的大小默认为16KB。为了保证页的连续性,InnoDB存储引擎每次从磁盘申请4-5个区
          • row:行,InnoDB存储引擎数据是按行进行存放的
            • Trx_id:每次对某条记录进行改动时,都会把对应的事务id赋值给Trx_id隐藏列
            • Roll_pointer:每次对某条记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息

2.架构

  • MySQL5.5之后,默认使用InnoDB存储引擎,它擅长事务处理,具有奔溃恢复特性,在日常开发中使用广泛

1.内存架构

1.Buffer Pool
  • 缓冲池是主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改查操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘加载并缓存),然后再以一定频率刷新到磁盘,从而减少磁盘IO,加快处理速度

  • 缓冲池以Page页为单位,底层采用链表数据结构管理Page。根据状态将Page分为三种类型:

    • free page:空闲page,未被使用
    • clean page:被使用page,数据没有被修改过
    • dirty page:脏页,被使用page,数据被修改过,页中数据与磁盘数据产生了不一致,还未刷新到磁盘
2.Change Buffer
  • 更改缓冲区(针对于非唯一二级索引页),在执行DML语句时,如果这些数据Page没有在Buffer Pool中,不会直接操作磁盘,而会将数据变更存在更改缓冲区Change Buffer中,在未来数据被读取时,再将数据合并恢复到Buffer Pool中,再将合并后的数据刷新到磁盘中

  • 与聚集索引不同,二级索引通常是非唯一的,并且以相对随机的顺序插入二级索引。同样,删除和更
    新可能会影响索引树中不相邻的二级索引页,如果每一次都操作磁盘,会造成大量的磁盘IO。有了
    ChangeBuffer之后,我们可以在缓冲池中进行合并处理,减少磁盘IO。

3.Adaptive Hash Index
  • 自适应hash索引,用于优化对Buffer Pool数据的查询。InnoDB存储引擎会监控对表上各索引页的查询,如果观察到hash索引可以提升速度,则建立hash索引,称之为自适应hash索引
  • 自适应哈希索引,无需人工干预,是系统根据情况自动完成。
  • 参数:adaptive_hash_index,可以查询是否开启
4.Log Buffer
  • 日志缓冲区,用来保存要写入到磁盘中的log日志数据(redo log、undo log),默认大小为 16MB,日志缓冲区的日志会定期刷新到磁盘中。如果需要更新、插入或删除许多行的事务,增加日志缓冲区的大小可以节省磁盘IO
  • 参数:
    • 缓冲区大小:innodb_log_buffer_size
    • 日志刷新到磁盘时机:innodb_flush_log_at_trx_commit
      • 1:日志在每次事务提交时写入并刷新磁盘
      • 0:每秒将日志写入并刷新磁盘一次
      • 2:日志在每次事务提交后写入,并每秒刷新到磁盘一次

2.磁盘结构

1.System Tablespace
  • 系统表空间是更改缓冲区的存储区域。如果表是在系统表空间而不是每个表文件或通用表空间中创建的,它也可能包含表和索引数据。(在MySQL5.x版本中还包含InnoDB数据字典、undolog等)
  • 参数:innodb_data_file_path
2.File-Per-Table Tablespaces
  • 每个表的文件表空间包含单个InnoDB表的数据和索引,并存储在文件系统上的单个数据文件中。
  • 参数:innodb_file_per_table

# 默认开启

3.General Tablespaces
  • 通用表空间,需要通过CREATE TABLESPACE 语法创建通用表空间,在创建表时,可以指定该表空间。

  • 语法:

  • 创建通用表空间

1
create tablespace xxxx add datafile '表空间关联的表空间文件' engine='引擎名称';
  • 指定表空间
1
create table xxx... tablespace 表空间名称;
4.Undo Tablespaces
  • 撤销表空间,MySQL实例在初始化时会自动创建两个默认的undo表空间(初始大小16M),用于存储undo log日志。
5.Temporary Tablespaces
  • InnoDB 使用会话临时表空间和全局临时表空间。存储用户创建的临时表等数据。
6.Doublewrite Buffer Files
  • 双写缓冲区,innoDB引擎将数据页从Buffer Pool刷新到磁盘前,先将数据页写入双写缓冲区文件中,便于系统异常时恢复数据。

  • dblwr后缀文件

7.Redo Log
  • 重做日志,是用来实现事务的持久性。该日志文件由两部分组成:

    • 重做日志缓冲(redo log buffer),在内存中
    • 重做日志文件(redo log)在磁盘中。
  • 当事务提交之后会把所有修改信息都会存到该日志中,用于在刷新脏页到磁盘时,发生错误时,进行数据恢复使用。

  • 以循环方式写入重做日志,涉及两个文件ib_logfile0和ib_logfile1

3.后台线程

1.Master Thread
  • 核心后台线程,负责调度其他线程,还负责将缓冲池中的数据异步刷新到磁盘中,保持数据的一致性,还包括脏页的刷新、合并插入缓存、undo页的回收。
2.IO Thread
  • 在InnoDB存储引擎中大量使用了AIO来处理IO请求,这样可以极大地提高数据库的性能,而IO Thread主要负素这些IO请求的回调。
线程类型 默认个数 职责
Read thread 4 负责读操作
Write thread 4 负责写操作
Log thread 1 负责将日志缓冲区刷新到磁盘
Insert buffer thread 1 负责将写缓冲区内容刷新到磁盘
3.Purge Thread
  • 主要用于回收事务已经提交了的undo log,在事务提交之后,undo log可能不用了,就用它来回收
4.Page Cleaner Thread
  • 协助Master Thread刷新脏页到磁盘的线程,它可以减轻Master Thread的工作压力,减少阻塞

4.总结

  • 业务操作时直接操作缓冲区中的数据,缓冲区中没有数据时,将磁盘中的数据加载回来再存储到缓冲区中,缓冲区中的数据再以一定的频率,通过后台线程刷新到磁盘中,在磁盘中进行永久化保留

3.事务原理

1.概述

  • 事务有四个特性:

    • 原子性
    • 一致性
    • 隔离性
    • 持久性
  • 其中原子性,一致性,持久性由redo log和undo log这两份日志来保证

  • 隔离性则由锁机制和MVCC多版本并发控制来实现

2.redo log

  • 重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。

  • 该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中,用于在刷新脏页到磁盘,发生错误时,进行数据恢复使用。

  • 这种先写日志再将脏页中的数据刷新到磁盘的机制称为WAL(Write-Ahead-Logging)

  • 保证了事务的持久性

3.undo log

  • 回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚和 MVCC(多版本并发控制)。
  • undo log和redo log记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录(updata之前的数据)。当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。
  • 保证了事务的原子性
  • Undo log销毁:undo log在事务执行时产生,事务提交时,并不会立即删除undo log,因为这些日志可能还用于MVCC。
  • Undo log存储:undo log采用段的方式进行管理和记录,存放在前面介绍的 rollback segment 回滚段中,内部包含1024个undo log segment。

4.MVCC

1.基本概念

1.当前读
  • 读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。对于日常操作,如select…lock in share mode(共享锁),select …for update,update,insert,delete(排他锁)都是一种当前读
2.快照读
  • 简单的select(不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。
  • Read Committed:每次select,都生成一个快照读。
  • Repeatable Read:开启事务后第一个select语句才是快照读的地方。
  • Serializable:快照读会退化为当前读。
3.MVCC
  • 全称Multi-Version Concurrency Control,多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,快照读为MySQL实现
  • MVCC提供了一个非阻塞读功能。MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log日志、readView。

2.实现原理

1.隐藏字段
隐藏字段 含义
DB_TRX_ID 最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID
DB_ROLL_PTR 回滚指针,指向这条记录的上一个版本,用于配合undo log,指向上一个版本
DB_ROW_ID 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段
2.undo log
  • 回滚日志,在insert、 update、delete的时候产生的便于数据回滚的日志。

    • 当insert的时候,产生的undo log日志只在回滚时需要,在事务提交后,可被立即删除。
    • 而update、delete的时候,产生的undo log不仅在回滚时需要,在快照读时也需要,不会立即被删除。
  • undo log版本链

  • 不同事务或相同事务对同一条记录进行修改,会导致该记录的undo log生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录

  • DB_ROLL_PTR指向上一个修改的记录

3.readview
  • readview(读视图)是快照读SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交)id
  • readview中包含四个核心字段:
字段 含义
m_ids 当前活跃的事务id集合
min_trx_id 最小活跃事务id
max_trx_id 预分配事务id,当前最大事务id+1(因为事务id是自增的)
creator_trx_id readview创建者的事务id
  • 版本链数据访问规则
    • trx_id==creator_trx_id?可以访问该版本
      • 说明数据是当前这个事务更改的
    • trx_id<min_trx_id?可以访问该版本
      • 说明数据已经提交了
    • trx_id>max_trx_id?不可以访问该版本
      • 说明该事务是再readview生成后才开启
    • min_trx_id<=trx_id<=max_trx_id?如果trx_id不在 m_ids中是可以访问该版本的
      • 说明数据已经提交

# trx_id:代表undo log中每一条数据的当前事务id

# 从最新的undo log记录,逐条数据进行比对,直到查找到符合规则的记录返回

  • 不同的隔离级别,生成readview的时机不同
    • rc:在事务每一次执行快照读时生成readview
    • rr:仅在事务中第一次执行快照读时生成readview,后续复用该readview

10.MySQL管理

1.系统数据库

  • 在MySQL数据库安装完后,自带了四个数据库
数据库 含义
mysql 存储MySQL服务器正常运行所需要的各种信息(时区,主从,用户,权限)
information_schema 提供了访问数据库元数据的各种表和视图,包含数据库,表,字段类型及访问权限等
performance_schema 为MySQL服务器运行时状态提供了一个底层监控功能,主要用于收集数据库服务器性能参数
sys 包含了一系列方便DBA和开发人员利用performance_schema性能数据库进行性能调优和诊断的视图

2.常用工具

1.mysql

  • mysql的客户端工具

  • 语法:

1
mysql 选项 数据库
  • 选项:

    • -u,指定用户名

    • -p,指定密码

    • -h,指定服务器IP或域名

    • -P,指定连接端口

    • -e,执行SQL语句并退出

      • -e这个选项可以在MySQL客户端执行SQL语句,而不用连接到MySQL数据库再执行,对于一些批处理脚本,这种方式较为方便

      • 例如:

1
mysql -uroot -p密码 数据库名 -e'sql语句'

2.mysqladmin

  • mysqladmin是一个执行管理操作的客户端程序,可以用它来检查服务器的配置和当前状态,创建并删除数据库等

  • 例如:

1
mysqladmin -uroot -p密码 drop 数据库名
1
mysqladmin -uroot -p密码 version
  • 可以通过帮助文档查看选项:
1
mysqladmin --help

3.mysqlbinlog

  • 由于服务器生成的二进制日志文件以二进制格式保存,所以如果想要检查这些文本的文本格式,就会使用到mysqlbinlog日志管理工具

  • 语法

1
mysqlbinlog 选项 binlog文件名1 binlog文件名2 ...
  • 选项:

    • -d,指定数据库名称,只列出指定的数据库相关操作
    • -o,忽略掉日志中的前n行命令
    • -r,将输出的文本格式日志输出到指定文件中
    • -s,显示简单格式,省略掉一些信息
    • –start-datetime=起始日期 --stop-datetime=截止日期,指定日期间隔内的所有日志
    • –start-positon=日志起始位置 --stop-position=日志截止位置,指定位置间隔内的所有日志

4.mysqlshow

  • mysqlshow客户端对象查找工具,用来快速查找存在哪些数据库,数据库中的表,表中的列或者索引

  • 语法:

1
mysqlshow 选项 数据库名称 表名称 字段名称
  • 选项:

    • –count,显示数据库及表的统计信息(数据库,表均可不指定)
    • -i,显示指定数据库或者指定表的状态信息
  • 例如:

1
mysqlshow -uroot -p密码 --count

# 查询每个数据库的表的数量及表中记录的数量

1
mysqlshow -uroot -p密码 数据库名 --count

# 查询指定数据库中每个表中的字段数及行数

1
mysqlshow -uroot -p密码 数据库名 表名 --count

# 查询指定数据库中指定表的详细情况

5.mysqldump

  • mysqldump客户端工具用来备份数据库或在不同数据库之间进行数据迁移。备份内容包含创建表,及插入表的SQL语句

  • 语法:

1
mysqldump 选项 数据库名 表名
1
mysqldump 选项 --database 数据库名1 数据库名2 ...

1
mysqldump 选项 -B 数据库名1 数据库名2 ...
1
mysqldump 选项 --all-databases

1
mysqldump 选项 -A

# 后面加**> 文件名.sql** 来指定保存到哪个文件

  • 选项:

    • 连接选项:

      • -u,指定用户名

      • -p,指定密码

      • -h,指定服务器IP或域名

      • -P,指定连接端口

    • 输出选项:

      • –add-drop-database,在每个数据库创建语句前加上drop database语句
      • –add-drop-table,在每个表创建语句前加上drop table语句,默认开启;不开启(–skip-add-drop-table)
      • -n,不包含数据库的创建语句
      • -t,不包含数据表的创建语句
      • -d,不包含数据
      • -T,自动生成两个文件:
        • .sql后缀文件,创建表结构的语句
        • .txt后缀文件,数据文件

6.mysqlimport/source

  • mysqlimport是客户端数据导入工具,用来导入mysqldump加-T参数后导出的文本文件

  • 语法:

1
mysqlimport 选项 数据库名 文件位置
  • 例如:
1
mysqlimport -uroot -p密码 数据库名 文件目录/文件名.txt
  • 如果需要导入sql文件,可以使用mysql中的source指令

  • 语法:

1
source /root/文件名.sql