Mysql入门学习_step1

任务一

研究MYSQL

包括:基本操作、数据库优化、分库分表、自动备份、自动同步、加密保护

路途博客

基本操作:

  • 主要针对自己不熟悉的操作,做系统的梳理

    • 联表查询的几种方式
    • 查询优化、查询效率检查方式
  • DDL (Data Definition Language): 数据定义语言,用来定义数据库对象(数据库、表、字段)

    • 数据库操作:查询所有数据库、查询当前数据库、创建数据库、删除数据库、使用数据库
    • 表操作:查询当前数据库所有表、查询表结构、查询指定表的建表语句、创建表、添加字段、修改数据类型、修改字段名和字段类型、删除字段、修改表名、删除表、删除表,并重新创建该表
  • DML (Data Manipulation Language): 数据操作语言,用来对数据库表中的数据进行增删改

    • 添加数据:指定字段、全部字段、批量添加数据
    • 更新和删除数据:修改数据、删除数据
  • DQL (Data Query Language): 数据查询语言,用来查询数据库中表的记录

    • 基础查询:查询多个字段、设置别名、去除重复记录、转义

    • 条件查询:在语句的条件组成中使用比较运算符、逻辑运算符

    • 聚合查询(聚合函数):count、max、min、avg、sum

    • 分组查询:

      • where 和 having 的区别:

        • 执行时机不同:where是分组之前进行过滤,不满足where条件不参与分组;having是分组后对结果进行过滤。
        • 判断条件不同:where不能对聚合函数进行判断,而having可以。
      • 注意事项
        • 执行顺序:where > 聚合函数 > having
        • 分组之后,查询的字段一般为聚合函数和分组字段,查询其他字段无任何意义
    • 排序查询:ASC(升序,默认方式),DESC(降序)

      • 注意事项:如果是多字段排序,当第一个字段值相同时,才会根据第二个字段进行排序
    • 分页查询:分页查询是数据库的方言,不同数据库有不同实现,MySQL是LIMIT

    • DQL语言的执行顺序:FROM -> WHERE -> GROUP BY -> SELECT -> ORDER BY -> LIMIT

    • 在这里插入图片描述

  • DCL (Data Control Language): 数据控制语言,用来创建数据库用户、控制数据库的控制权限

    • 管理用户:查询用户、创建用户、修改用户密码、删除用户
      • 注意事项:主机名可以使用 % 通配,表示能在任意主机访问
    • 权限控制:常用权限、查询权限、授予权限、撤销权限
      • 注意事项:
        • 授予多个权限时用逗号分隔、
        • 授权时,数据库名和表名可以用 * 进行通配,代表所有
  • 定义 – 操作 – 查询 – 控制

函数
  • 字符串函数
  • 数值函数
  • 日期函数
  • 流程函数
约束

分类:

约束描述关键字
非空约束限制该字段的数据不能为nullNOT NULL
唯一约束保证该字段的所有数据都是唯一、不重复的UNIQUE
主键约束主键是一行数据的唯一标识,要求非空且唯一PRIMARY KEY
默认约束保存数据时,如果未指定该字段的值,则采用默认值DEFAULT
检查约束(8.0.1版本后)保证字段值满足某一个条件CHECK
外键约束用来让两张图的数据之间建立连接,保证数据的一致性和完整性FOREIGN KEY

约束是作用于表中字段上的,可以再创建表/修改表的时候添加约束。

多表查询
  • 多表关系

    • 一对多(多对一)
    • 多对多
    • 一对一
  • 查询:合并查询(笛卡尔积,会展示所有组合结果)、消除无效笛卡尔积

  • 内连接查询:内连接查询的是两张表交集的部分(显式性能比隐式高)

    • 显示连接特征:inner join … on
    • 隐式连接特征:只对连接的两张表做重命名,没有其他关键字
  • 外连接查询

    • 左外连接:查询左表所有数据,以及两张表交集部分数据
    • 右外连接:查询右表所有数据,以及两张表交集部分数据
    • <左或右全数据> + 两表交集
  • 自连接查询:当前表与自身的连接查询,自连接必须使用表别名

    • 自连接查询,可以是内连接查询,也可以是外连接查询
    • 创建表别名:关键字as + 别名,或者直接空格 + 别名(两者没有区别)
  • 联合查询:把多次查询(select)的结果合并,形成一个新的查询集

    • UNION ALL 会有重复行,UNION 不会
    • 联合查询比使用or效率高,不会使索引失效
  • 子查询:SQL语句中嵌套SELECT语句,称谓嵌套查询,又称子查询。

    • 根据子查询结果可以分为:
      • 标量子查询(子查询结果为单个值) 常用操作符:- < > > >= < <=
      • 列子查询(子查询结果为一列) 常用操作符:IN , NOT IN, ANY, SOME, ALL
      • 行子查询(子查询结果为一行) 常用操作符:=, <, >, IN, NOT IN
      • 表子查询(子查询结果为多行多列) 常用操作符:IN
    • 根据子查询位置可分为:
      • WHERE 之后
      • FROM 之后
      • SELECT 之后
✨事务

事务是一组操作的集合,事务会把所有操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。

  • 四大特性ACID

    • 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败
    • 一致性(Consistency):事务完成时,必须使所有数据都保持一致状态
    • 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行
    • 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的
  • 并发事务

  • 问题描述
    脏读一个事务读到另一个事务还没提交的数据
    不可重复读一个事务先后读取同一条记录,但两次读取的数据不同
    幻读一个事务按照条件查询数据时,没有对应的数据行,但是再插入数据时,又发现这行数据已经存在
    (查询时没有,插入时提示已存在)
  • 从低到高划分为以下四种:

事务隔离级别实现方式
未提交读(RU)事务对当前被读取的数据不加锁,都是当前读; 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级共享锁,直到事务结束才释放。
提交读(RC)事务对当前被读取的数据不加锁,且是快照读; 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁(Record),直到事务结束才释放。 通过快照,在这个级别MySQL就解决了不可重复读的问题
可重复读(RR)事务对当前被读取的数据不加锁,且是快照读; 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁(Record,GAP,Next-Key),直到事务结束才释放。 通过间隙锁,在这个级别MySQL就解决了幻读的问题
序列化读(S)事务在读取数据时,必须先对其加表级共享锁 ,直到事务结束才释放,都是当前读; 事务在更新数据时,必须先对其加表级排他锁 ,直到事务结束才释放。

查询当前隔离级别:

  • mysql> show variables like ‘transaction%’;
  • mysql> select @@transaction_isolation;

可以使用以下语句设置session的隔离级别:

  • mysql> set session transaction isolation level read committed;
  • 能够选择隔离级别:REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED | SERIALIZABLE

补充:

需要注意的是,更改隔离级别只会影响当前session中的事务隔离级别,并不会影响其他session中的隔离级别。另外,隔离级别的设置会影响并发性能和数据一致性,需要在实际应用中根据需要进行权衡和选择。

可以从低到高划分为以下四种:读未提交、读已提交、可重复读、序列化读

脏读(Dirty Read)、不可重复读(Non-Repeatable Read)、幻读(Phantom Read)是三种并发事务中可能会出现的问题。

  • 脏读:当一个事务读取到了另一个事务还未提交的数据,而后者最终未提交成功时,前者读取到的数据就是“脏数据”。解决方案是使用隔离级别 SERIALIZABLE(串行化隔离级别),这个隔离级别能够确保事务串行执行,避免了脏读的问题。

  • 不可重复读:当一个事务读取了另一个事务已经提交的数据,而后者在此之后又修改或删除了该数据,导致前者再次读取时数据已经发生了变化,因此出现了不一致的现象。解决方案是使用隔离级别 REPEATABLE READ(可重复读隔离级别),这个隔离级别可以确保事务执行期间不会出现数据被修改或删除的情况,因此能够避免不可重复读的问题。

  • 幻读:当一个事务执行了一个查询操作,返回了一组数据,但是在事务执行期间,另一个事务插入了符合该查询条件的一条新数据,导致前者再次执行查询操作时,返回的数据集合中多出了新插入的那一条数据,因此出现了幻读的现象。解决方案是使用隔离级别 REPEATABLE READ 或 SERIALIZABLE,这两个隔离级别都能够确保事务执行期间不会有新数据被插入,因此能够避免幻读的问题。

需要注意的是,隔离级别越高并不一定越好。隔离级别高会增加锁的使用和开销,可能会导致性能下降和并发度降低,而且有些隔离级别也无法完全避免某些问题。因此,在选择隔离级别时,需要根据具体业务场景和需求进行权衡。

数据库优化:

存储引擎

MySQL体系结构

  • 连接层
    最上层是一些客户端和链接服务,包含本地sock 通信和大多数基于客户端/服务端工具实现的类似于TCP/IP的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。

  • 服务层
    第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如 过程、函数等。在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定表的查询的顺序,是否利用索引等,最后生成相应的执行操作。如果是select语句,服务器还会查询内部的缓存,如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。

  • 引擎层
    存储引擎层, 存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。数据库中的索引是在存储引擎层实现的。

  • 存储层
    数据存储层, 主要是将数据(如: redolog、undolog、数据、索引、二进制日志、错误日志、查询日志、慢查询日志等)存储在文件系统之上,并完成与存储引擎的交互。

和其他数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎上,插件式的存储引擎架构,将查询处理和其他的系统任务以及数据的存储提取分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。

存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的,而不是基于库的,所以存储引擎也可被称为表类型。我们可以在创建表的时候,来指定选择的存储引擎,如果没有指定将自动选择默认的存储引擎。

建表时指定存储引擎 create table <tablename>(...) ENGINE = INNODB [ COMMENT 表注释 ] ;

查询当前数据库支持的存储引擎 mysql> show engines

查询建表语句 — 默认存储引擎: InnoDB show create table account;

查询当前数据库支持的存储引擎 show engines ;

存储引擎特点

InnoDB

  1. ) 介绍:

    • InnoDB是一种兼顾高可靠性和高性能的通用存储引擎,在 MySQL 5.5 之后,InnoDB是默认的MySQL 存储引擎。
  2. ) 特点

    • DML操作遵循ACID模型,支持事务;
    • 行级锁,提高并发访问性能;
    • 支持外键FOREIGN KEY约束,保证数据的完整性和正确性;
  3. ) 文件

    • xxx.ibdxxx代表的是表名,innoDB引擎的每张表都会对应这样一个表空间文件,存储该表的表结构(frm-早期的 、sdi-新版的)、数据和索引。
    • 参数:innodb_file_per_table
      • 查看是否开启(ON) show variables like 'innodb_file_per_table'
    • 如果该参数开启,代表对于InnoDB引擎的表,每一张表都对应一个ibd文件。 我们直接打开MySQL的数据存放目录: C:\ProgramData\MySQL\MySQL Server 8.0\Data , 这个目录下有很多文件夹,不同的文件夹代表不同的数据库。
      • ibd文件中不仅存放表结构、数据,还会存放该表对应的索引信息。 而该文件是基于二进制存储的,不能直接基于记事本打开,我们可以使用mysql提供的一个指令 ibd2sdi ,通过该指令就可以从ibd文件中提取sdi信息,而sdi数据字典信息中就包含该表的表结构。
  4. )逻辑存储结构

  • 表空间 : InnoDB存储引擎逻辑结构的最高层,ibd文件其实就是表空间文件,在表空间中可以包含多个Segment段。
  • 段 : 表空间是由各个段组成的, 常见的段有数据段、索引段、回滚段等。InnoDB中对于段的管理,都是引擎自身完成,不需要人为对其控制,一个段中包含多个区。
  • 区 : 区是表空间的单元结构,每个区的大小为1M。 默认情况下, InnoDB存储引擎页大小为16K, 即一个区中一共有64个连续的页。
  • 页 : 页是组成区的最小单元,页也是InnoDB存储引擎磁盘管理的最小单元,每个页的大小默认为 16KB。为了保证页的连续性,InnoDB存储引擎每次从磁盘申请 4-5 个区。
  • 行 : InnoDB存储引擎是面向行的,也就是说数据是按行进行存放的,在每一行中除了定义表时所指定的字段以外,还包含两个隐藏字段(后面会详细介绍)。

MyISAM

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

特点:

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

文件:

  • xxx.sdi: 存储表结构信息
  • xxx.MYD: 存储数据
  • xxx.MYI: 存储索引

Memory

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

特点:

  • 存放在内存中,速度快
  • hash索引(默认)

文件:

  • xxx.sdi: 存储表结构信息

InnoDB引擎与MyISAM引擎的区别 ?
①. InnoDB引擎, 支持事务, 而MyISAM不支持。
②. InnoDB引擎, 支持行锁和表锁, 而MyISAM仅支持表锁, 不支持行锁。
③. InnoDB引擎, 支持外键, 而MyISAM是不支持的。

存储引擎的选择

在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。

  • InnoDB: 如果应用对事物的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操作,则 InnoDB 是比较合适的选择
  • MyISAM: 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不高,那这个存储引擎是非常合适的。
  • Memory: 将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。Memory 的缺陷是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性
    • 如果内存出现异常会影响到数据的完整性。
    • 如果重启机器或者关机,表中的所有数据都将消失,因此,基于Memory存储引擎的表的生命周期都比较短,一般都是一次性的。

电商中的足迹和评论适合使用 MyISAM 引擎,缓存适合使用 Memory 引擎。

索引

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

优缺点:

优点:

  • 提高数据检索效率,降低数据库的IO成本
  • 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗

缺点:

  • 索引列也是要占用空间的
  • 索引大大提高了查询效率,但降低了更新的速度,比如 INSERT、UPDATE、DELETE

索引结构

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

二叉树的缺点可以用红黑树来解决:

红黑树也存在大数据量情况下,层级较深,检索速度慢的问题。

为了解决上述问题,可以使用 B-Tree 结构。
B-Tree (多路平衡查找树) 以一棵最大度数(max-degree,指一个节点的子节点个数)为5(5阶)的 b-tree 为例(每个节点最多存储4个key,5个指针)

B-Tree 的数据插入过程动画参照:https://www.bilibili.com/video/BV1Kr4y1i7ru?p=68
演示地址:https://www.cs.usfca.edu/~galles/visualization/BTree.html

✨B+Tree

结构图:

演示地址:https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html

与 B-Tree 的区别:

  • 所有的数据都会出现在叶子节点
  • 叶子节点形成一个单向链表

MySQL 索引数据结构对经典的 B+Tree 进行了优化。在原 B+Tree 的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的 B+Tree,提高区间访问的性能。

  • 最终我们看到,B+Tree 与 B-Tree相比,主要有以下三点区别:
    • 所有的数据都会出现在叶子节点。
    • 叶子节点形成一个单向链表。
    • 非叶子节点仅仅起到索引数据作用,具体的数据都是在叶子节点存放的
Hash

哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中。
如果两个(或多个)键值,映射到一个相同的槽位上,他们就产生了hash冲突(也称为hash碰撞),可以通过链表来解决。

特点:

  • Hash索引只能用于对等比较(=、in),不支持范围查询(betwwn、>、<、…)
  • 无法利用索引完成排序操作
  • 查询效率高,通常只需要一次检索就可以了,效率通常要高于 B+Tree 索引

存储引擎支持:

  • Memory
  • InnoDB: 具有自适应hash功能,hash索引是存储引擎根据 B+Tree 索引在指定条件下自动构建的
面试题
  1. 为什么 InnoDB 存储引擎选择使用 B+Tree 索引结构?
  • 相对于二叉树,层级更少,搜索效率高
  • 对于 B-Tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针也跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低
  • 相对于 Hash 索引,B+Tree 支持范围匹配及排序操作

补充:

  • 非叶子节点只存储索引信息,而不存储数据信息,这样可以使得一个节点可以存储更多的索引信息,从而减少树的深度。
  • 叶子节点之间通过双向链表连接,这样可以使得范围查询更加高效,同时也减少了树的深度。
  • B+树的节点大小通常比较大,一般为16KB或者32KB,这样可以使得每个节点可以存储更多的索引信息,从而减少树的深度。

索引分类

分类含义特点关键字
主键索引针对于表中主键创建的索引默认自动创建,只能有一个PRIMARY
唯一索引避免同一个表中某数据列中的值重复可以有多个UNIQUE
常规索引快速定位特定数据可以有多个
全文索引全文索引查找的是文本中的关键词,而不是比较索引中的值可以有多个FULLTEXT

在 InnoDB 存储引擎中,根据索引的存储形式,又可以分为以下两种:

分类含义特点
聚集索引(Clustered Index)将数据存储与索引放一块,索引结构的叶子节点保存了行数据必须有,而且只有一个
二级索引(Secondary Index)将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键可以存在多个

演示图:

  • 回表查询: 这种先到二级索引中查找数据,找到主键值,然后再到聚集索引中根据主键值,获取数据的方式,就称之为回表查询。

  • 聚集索引的叶子节点下挂的是这一行的数据 。

  • 二级索引的叶子节点下挂的是该字段值对应的主键值。

聚集索引选取规则:

  • 如果存在主键,主键索引就是聚集索引
  • 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引
  • 如果表没有主键或没有合适的唯一索引,则 InnoDB 会自动生成一个 rowid 作为隐藏的聚集索引
思考题

1. 以下 SQL 语句,哪个执行效率高?为什么?

select * from user where id = 10;
select * from user where name = 'Arm';
-- 备注:id为主键,name字段创建的有索引

答:第一条语句,因为第二条需要回表查询,相当于两个步骤。

2. InnoDB 主键索引的 B+Tree 高度为多少?

答:假设一行数据大小为1k,一页中可以存储16行这样的数据。InnoDB 的指针占用6个字节的空间,主键假设为bigint,占用字节数为8.
可得公式:n * 8 + (n + 1) * 6 = 16 * 1024

  • 其中 8 表示 bigint 占用的字节数,
  • n 表示当前节点存储的key的数量,
  • (n + 1) 表示指针数量(比key多一个)。算出n约为1170。

如果树的高度为2,那么他能存储的数据量大概为:1171 * 16 = 18736
如果树的高度为3,那么他能存储的数据量大概为:1171 * 1171 * 16 = 21939856

另外,如果有成千上万的数据,那么就要考虑分表,涉及运维篇知识。

语法

创建索引:
CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name (index_col_name, ...);
如果不加 CREATE 后面不加索引类型参数,则创建的是常规索引

查看索引:
SHOW INDEX FROM table_name;

删除索引:
DROP INDEX index_name ON table_name;

案例:

-- name字段为姓名字段,该字段的值可能会重复,为该字段创建索引
create index idx_user_name on tb_user(name);
-- phone手机号字段的值非空,且唯一,为该字段创建唯一索引
create unique index idx_user_phone on tb_user (phone);
-- 为profession, age, status创建联合索引
create index idx_user_pro_age_stat on tb_user(profession, age, status);
-- 为email建立合适的索引来提升查询效率
create index idx_user_email on tb_user(email);

-- 删除索引
drop index idx_user_email on tb_user;
  • MySQL自动选择单列索引导致回表查询

  • or导致索引失效的情况

  • SQL提示

  • 查询前缀索引选择性公式

  • 查询时MySQL的自评估

  • 创建索引后再次对该栏做条件查询

  • 创建索引时能够指定升序索引还是降序索引

  • 大于等于或小于等于不会导致的索引失效

  • 大于小于导致的最右索引失效

  • 对无索引栏做条件查询

  • 模糊查询可能导致索引失效的情况

  • 区分升降序查询的情况建立两组联合索引

  • 使用主键id进行查询

  • 数据分布影响索引使用

  • 为什么需要使用覆盖索引

  • 字符串不加引号导致的字段索引失效

  • 最左前缀法则-联合索引生效的情况

  • 最左前缀法则-联合索引失效的情况

SQL性能分析

SHOW GLOBAL STATUS LIKE 'Com_______' 7个下划线符号

Com_delete: 删除次数;Com_insert: 插入次数;Com_select: 查询次数; …

通过上述指令,我们可以查看到当前数据库到底是以查询为主,还是以增删改为主,从而为数据库优化提供参考依据。 如果是以增删改为主,我们可以考虑不对其进行索引的优化。 如果是以查询为主,那么就要考虑对数据库的索引进行优化了。

  • 慢查询

    • #MySQL的慢查询日志默认没有开启,可以通过查看系统变量 slow_query_log 确认。 
      mysql> show variables like 'slow_query_log';
      
      #开启慢查询日志,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:
      # 开启MySQL慢日志查询开关
      slow_query_log=1
      # 设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志;window下配置默认为10秒,linux需要手动追加
      long_query_time=2
      
      #配置完毕之后,通过以下指令重新启动MySQL服务器进行测试,查看慢日志文件中记录的信息  /var/lib/mysql/localhost-slow.log。
      systemctl restart mysqld
      
    • 慢查询日志内容组成结构

  • profiles

    • profile的确认开启步骤

    • profiles具体的使用

    • profiles具体的使用-2

  • desc/explain

    desc或explain某条mysql查询语句之后表格有
    mysql> desc select * from tb_user where id = 1;
    +----+-------------+---------+------------+------+---------------+---------------+---------+-------+------+----------+-------+
    | id | select_type | table   | partitions | type | possible_keys | key           | key_len | ref   | rows | filtered | Extra |
    +----+-------------+---------+------------+------+---------------+---------------+---------+-------+------+----------+-------+
    
    id: 表示查询中执行 SELECT 语句或子查询的序号,通常从 1 开始依次递增,如果查询中包含了子查询,那么每个子查询会有一个唯一的 ID。
    select_type: 表示 SELECT 操作的类型,通常有以下几种类型:
        - SIMPLE: 普通的 SELECT 查询,不包含 UNION 和子查询等。
        - PRIMARY: 表示外层查询。
        - SUBQUERY: 子查询中的第一个 SELECT。
        - DEPENDENT SUBQUERY: 子查询中的第一个 SELECT,依赖于外部的查询。
        - UNION: UNION 中的第二个或之后的 SELECT 查询。
        - DEPENDENT UNION: UNION 中的第二个或之后的 SELECT 查询,依赖于外部的查询。
        - UNION RESULT: UNION 的结果集。
        - DERIVED: 表示从 FROM 子句中派生的表,MySQL 会将这个查询看作一个临时表来处理。
    table: 显示当前行正在访问哪个表。
    partitions: 显示当前行所访问的表的分区情况。
    type: 表示访问表时使用的查询类型,MySQL 中常见的查询类型有以下几种:
        - ALL: 表示全表扫描,即遍历整个表中的每一行记录。
        - index: 表示全索引扫描,即遍历整个索引树,而不是表中的行。
        - range: 表示使用索引来检索行,检索结果是索引的一个范围。
        - ref: 表示使用非唯一索引或唯一索引的前缀来匹配某些值,返回的行数较少。
        - eq_ref: 表示使用唯一索引或主键来匹配某些值,返回的行数为 1。
        - const: 表示使用常量表达式进行匹配,返回的行数为 1。
        - system: 表示表中只有一行记录(例如 mysql 系统表),通常只有在查询时使用主键时才会出现这种类型。
    possible_keys: 显示可能用于查询的索引列表,这些索引并不一定被 MySQL 实际使用。
    key: 显示 MySQL 实际使用的索引。
    key_len: 表示索引中使用的字节数, 该值为索引字段最大可能长度,并非实际使用长
    度,在不损失精确性的前提下, 长度越短越好 。
    ref: 显示索引的哪些列或常量被使用了。
    rows: 表示 MySQL 通过执行查询使用的索引所估算出的需要读取的行数。
    filtered: 表示返回结果的行数占需读取行数的百分比,即通过索引扫描后不需要的行数与索引,filtered 的值越大越好。
    Extra: 表示其他的一些信息,其中包含了一些非常有用的信息
        Using filesort: 表示 MySQL 需要对结果集进行排序操作,而排序过程中需要使用临时文件。
        Using temporary: 表示 MySQL 需要创建一个临时表来处理查询结果。
        Using index: 表示 MySQL 使用了覆盖索引,即查询结果可以直接从索引中获取,而不需要再访问数据表。
        Using where: 表示 MySQL 使用了 WHERE 子句来限制查询的结果集。
        Using join buffer: 表示 MySQL 需要使用到 join buffer 来处理连接查询。
        Impossible where: 表示查询的 WHERE 子句不可能返回任何结果。
        Select tables optimized away: 表示 MySQL 在处理查询时发现可以直接从索引中获取所需要的数据,而不需要再访问数据表。
        No tables used: 表示查询没有访问任何数据表,通常是因为使用了函数或者常量表达式等。
        Distinct: 表示查询需要去重操作,即返回的结果集中不能包含重复的行。
    
最左前缀法则

如果索引关联了多列(联合索引),要遵守最左前缀法则,最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。
如果跳跃某一列,索引将部分失效(后面的字段索引失效)。

联合索引中,出现范围查询(<, >),范围查询右侧的列索引失效。可以用>=或者<=来规避索引失效问题。

为profession , age , status三个字段建立联合索引:

  • mysql> create index idx_user_pro_age_sta on tb_user(profession , age , status);

以上的这三组测试中,我们发现只要联合索引最左边的字段 profession存在,索引就会生效,只不
过索引的长度不同。

  • 联合索引中的最左字段 profession 只要出现在查询语句中,就至少有一个索引会被使用

    • 如果联合索引的字段都出现在一条查询语句中,索引将都被使用
    • 如果只出现了profession 和 status 字段那么被跳过的 age 字段和它之后的索引,这里只有一个status字段,索引都将不会被调用
  • 索引是否被调用可以通过从左向右字段,多次查询,依次添加的,使用explain工具检查查询语句,以此来推出个字段的key_len

    • 例如:由以上三组测试,分别查询 (profession)、(profession、age)、(profession、age、status) 我们即可推测出profession字段索引长度为47、age字段索引长度为2、status字段索引长度为5。
  • 注意 : 最左前缀法则中指的最左边的列,是指在查询时,联合索引的最左边的字段(即是第一个字段)必须存在,与我们编写SQL时,条件编写的先后顺序无关。

范围查询

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

联合索引中,出现范围查询(>=,<=,=),索引不会失效。

索引失效情况
  1. 在索引列上进行运算操作,索引将失效。如:explain select * from tb_user where substring(phone, 10, 2) = '15';
  2. 字符串类型字段使用时,不加引号,索引将失效。如:explain select * from tb_user where phone = 17799990015;,此处phone的值没有加引号
    • 情况针对 int 类型转换字符类型(varchar、char)
  3. 模糊查询中,如果仅仅是尾部模糊匹配,索引不会是失效;如果是头部模糊匹配,索引失效。如:explain select * from tb_user where profession like '%工程';,前后都有 % 也会失效。
  4. 用 or 分割开的条件,如果 or 其中一个条件的列没有索引,那么涉及的索引都不会被用到。
    • 当or连接的条件,左右两侧字段都有索引时,索引才会生效。
  5. 如果 MySQL 评估使用索引比全表扫描更慢,则不使用索引。例如判断is nullis not null,一般需要考虑的因素如下
    • 索引的选择性:选择性是指索引列上不同值的数量与总行数的比例。如果索引列具有较低的选择性,即有很多行具有相同的索引值,则使用索引可能不如全表扫描快。
    • 数据分布:如果大多数行具有非空值,则使用索引可能更快。然而,如果大多数行具有空值,则使用索引可能会变得更慢,因为需要扫描索引中的大量条目。
    • 查询条件的复杂性:如果查询包含多个条件,则使用索引可能更快,因为可以使用索引上的多列进行筛选和排序。然而,如果查询包含复杂的条件(例如,使用函数或运算符),则使用索引可能不如全表扫描快。
    • 表的大小:对于小表,全表扫描可能更快,而对于大表,使用索引通常更快。
SQL 提示
  • 大前提:一个字段可以建立多个指引,以下描述都是基于两个或以上的索引

是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。

例如,使用索引:
explain select * from tb_user use index(idx_user_pro) where profession="软件工程";
不使用哪个索引:
explain select * from tb_user ignore index(idx_user_pro) where profession="软件工程";
必须使用哪个索引:
explain select * from tb_user force index(idx_user_pro) where profession="软件工程";

use 是建议,实际使用哪个索引 MySQL 还会自己权衡运行速度去更改,force就是无论如何都强制使用该索引。

覆盖索引&回表查询
  • 聚集索引:通过主键作为条件查询,之后内容有描述覆盖索引的选取规则

    ![](https://img-blog.csdnimg.cn/7ae8537bd64b43d78c120e5ddef5e1de.png)
    
  • 二级索引:使用普通索引作为查询条件;叶子节点中,主键id和建立的普通索引的值放在一起

  • 回表查询:查询条件是普通索引,会先经历二级索引查询,查询内容中存在无索引字段

因为,在tb_user表中有一个联合索引 idx_user_pro_age_sta,该索引关联了三个
字段profession、age、status,这是一个二级索引,所以和叶子节一起存放的是
这一行的主键id。

所以当我们查询返回的数据在 id、profession、age、status 之中,则使用二级
索引可以直接返回数据。 如果超出这个范围,就需要拿到主键id,再去扫描聚集索引
,再获取额外的数据了,这个过程就是回表。

而我们如果一直使用select * 查询返回所有字段值,很容易就会造成回表查询(除非
是根据主键查询,此时只会扫描聚集索引)。所以建议尽量使用覆盖索引

尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能找到),减少 select *。

explain 中 extra 字段含义:
using index codition:查找使用了索引,但是需要回表查询数据
using where; using index;:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询

如果在聚集索引中直接能找到对应的行,则直接返回行数据,只需要一次查询,哪怕是select *;如果在辅助索引中找聚集索引,如select id, name from xxx where name='xxx';,也只需要通过辅助索引(name)查找到对应的id,返回name和name索引对应的id即可,只需要一次查询;如果是通过辅助索引查找其他字段,则需要回表查询,如select id, name, gender from xxx where name='xxx';

所以尽量不要用select *,容易出现回表查询,降低效率,除非有联合索引包含了所有字段

面试题:一张表,有四个字段(id, username, password, status),由于数据量大,需要对以下SQL语句进行优化,该如何进行才是最优方案:
select id, username, password from tb_user where username='itcast';

解:给username和password字段建立联合索引,则不需要回表查询,直接覆盖索引

前缀索引
  • 在 MySQL 中,字符串的索引起始位置是从 1 开始的,但在 C++ 和 Go 等其他编程语言中,字符串的索引起始位置通常是从 0 开始的。

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

语法:create index idx_xxxx on table_name(columnn(n));
前缀长度:可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。

  • 求选择性公式:
select count(distinct email) / count(*) from tb_user;
select count(distinct substring(email, 1, 5)) / count(*) from tb_user;

show index 里面的sub_part可以看到接取的长度

单列索引&联合索引

单列索引:即一个索引只包含单个列
联合索引:即一个索引包含了多个列
在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引。

单列索引情况:
explain select id, phone, name from tb_user where phone = '17799990010' and name = '韩信';
这句只会用到phone索引字段

注意事项

  • 多条件联合查询时,MySQL优化器会评估哪个字段的索引效率更高,会选择该索引完成本次查询

设计原则

  1. 针对于数据量较大,且查询比较频繁的表建立索引
  2. 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
  3. 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高
  4. 如果是字符串类型的字段,字段长度较长,可以针对于字段的特点,建立前缀索引
  5. 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率
  6. 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价就越大,会影响增删改的效率
  7. 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询

索引会导致增删改变慢的主要原因有以下几个方面:

  1. 索引的维护:在增删改操作中,如果涉及到索引列,那么除了修改数据本身之外,还需要维护索引。插入新数据时,需要插入新的索引值;删除数据时,需要删除相应的索引值;更新数据时,需要更新旧的索引值。这些索引的维护会占用一定的时间和资源,从而导致增删改变慢。
  2. 索引的空间占用:索引需要占用一定的磁盘空间,当表中数据量增加时,索引的空间占用也会相应增加。如果磁盘空间不足或者索引过大,可能会导致增删改操作变慢。
  3. 索引的更新可能会导致锁定:在 InnoDB 存储引擎中,更新操作会涉及到行锁和表锁。如果要更新的数据行被锁定了,其他事务就无法修改该行数据,从而导致操作阻塞。

综上所述,索引可能会导致增删改变慢。但是,索引的好处是能够大幅度提高查询操作的性能,因此需要根据具体业务需求和数据特点来进行权衡。如果读操作远远多于写操作,且查询需要频繁地使用索引,那么建立索引可能会带来更大的好处。如果写操作远远多于读操作,或者表中的数据量比较小,那么可以考虑减少索引的数量,从而提高写操作的性能。

sql优化

插入数据

普通插入:

  1. 采用批量插入(一次插入的数据不建议超过1000条)

  2. 手动提交事务

    • 当需要插入大量数据时,如果每次插入一条数据,就会涉及到数据库的磁盘 I/O 操作和日志写入操作,这些操作会带来一定的性能开销。为了减少这些开销,可以使用事务来控制插入操作。事务可以将多个插入操作封装在一个单元中,然后在所有插入操作完成后一次性提交事务。这样,就可以减少日志写入操作和磁盘 I/O 操作的次数,从而提高插入性能。
    • 另外,手动控制事务还可以对数据一致性和完整性进行保障,确保在插入操作发生异常时,能够将数据回滚到事务开始前的状态,避免数据的不一致性和丢失。这也是使用手动控制事务的一个重要原因。
    • 需要注意的是,在使用手动控制事务时,需要合理设置事务的范围和隔离级别,避免出现死锁和性能下降的情况。同时,也需要根据具体的应用场景,权衡事务的一致性和效率之间的关系,选择合适的方案进行优化。
  3. 主键顺序插入

    • 设置主键为自增长来实现主键顺序插入

    • 对比可能导致乱序插入的情况

      1. 并发插入:多个线程或进程同时插入记录时,由于插入操作不是原子操作,可能导致插入顺序不一致,从而产生主键乱序插入的情况。
      2. 手动插入:如果手动插入记录,没有使用自增长主键或没有按照主键顺序插入,就会导致主键乱序插入的情况。
      3. 主键重置:如果重置了自增长主键的值,就会导致主键乱序插入的情况。

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

# 客户端连接服务端时,加上参数 --local-infile(这一行在bash/cmd界面输入)
mysql --local-infile -u root -p
# 设置全局参数local_infile为1,开启从本地加载文件导入数据的开关
set global local_infile = 1;
select @@local_infile;
# 执行load指令将准备好的数据,加载到表结构中
load data local infile '/root/sql1.log' into table 'tb_user' fields terminated by ',' lines terminated by '\n';

  • 主键顺序插入的性能是要高于乱序插入的

主键优化

  1. 数据组织方式

    在InnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(index organized table IOT)。

    行数据,都是存储在聚集索引的叶子节点上的。而我们之前也讲解过InnoDB的逻辑结构图:

    在InnoDB引擎中,数据行是记录在逻辑结构 page 页中的,而每一个页的大小是固定的,默认16K。

    那也就意味着, 一个页中所存储的行也是有限的,如果插入的数据行row在该页存储不小,将会存储到下一个页中,页与页之间会通过指针连接。

  2. 页分裂
    页可以为空,也可以填充一半,也可以填充100%。每个页包含了2-N行数据(如果一行数据过大,会行溢出),根据主键排列。

A. 主键顺序插入效果

  1. ) 从磁盘中申请页, 主键顺序插入

  2. )第一个页没有满,继续往第一页插入

  3. )当第一个也写满之后,再写入第二个页,页与页之间会通过指针连接

  4. )当第二页写满了,再往第三页写入

B. 主键乱序插入效果

  • 如此时再插入id为50的记录,因为,索引结构的叶子节点是有顺序的。按照顺序,应该存储在47之后。
  • 乱序可能出现情况,47在第一页的最后一个,55在第二页的第一个
    • 此时会开辟一个新的页 3#。
    • 但是并不会直接将50存入3#页,而是会将1#页后一半的数据,移动到3#页,然后在3#页,插入50。
    • 移动数据,并插入id为50的数据之后,那么此时,这三个页之间的数据顺序是有问题的。 1#的下一个页,应该是3#, 3#的下一个页是2#。 所以,此时,还需要重新设置链表指针。
  • 上述的这种现象,称之为 “页分裂”,是比较耗费性能的操作。
  1. 页合并
    • 当我们对已有数据进行删除时,
      • 当删除一行记录时,实际上记录并没有被物理删除,只是记录被标记(flaged)为删除并且它的空间变得允许被其他记录声明使用。
    • 当我们继续删除2#的数据记录
      • 当页中删除的记录达到 MERGE_THRESHOLD(默认为页的50%),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间使用。
        • MERGE_THRESHOLD:合并页的阈值,可以自己设置,在创建表或者创建索引时指定。
      • 删除数据,并将页合并之后,再次插入新的数据21,则直接插入3#页
    • 这个里面所发生的合并页的这个现象,就称之为 “页合并”。
  2. 索引设计原则
    • 满足业务需求的情况下,尽量降低主键的长度。
    • 插入数据时,尽量选择顺序插入,选择使用AUTO_INCREMENT自增主键
    • 尽量不要使用UUID做主键或者是其他自然主键,如身份证号。
    • 业务操作时,避免对主键的修改。

order by优化

  • Using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区 sort buffer 中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序
  • Using index:通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高

如果order by字段全部使用升序排序或者降序排序,则都会走索引,但是如果一个字段升序排序,另一个字段降序排序,则不会走索引,explain的extra信息显示的是Using index, Using filesort,如果要优化掉Using filesort,则需要另外再创建一个索引,如:create index idx_user_age_phone_ad on tb_user(age asc, phone desc);,此时使用select id, age, phone from tb_user order by age asc, phone desc;会全部走索引

针对两个参数 age,phone 进行排序,查询内容为 主键 id、age、phone 可能情况有

  • 无索引
    • 无论升序还是降序 Extra:Using filesort
  • 建立联合索引create index idx_user_age_phone_aa on tb_user(age,phone);
    • order by age , phone;,Extra:Using index
    • order by age desc, phone desc;,Extra:Backward index scan; Using index
    • order by phone, age;,Extra: Using index; Using filesort
    • order by phone, age;,Extra: Using index; Using filesort
    • order by age asc , phone desc ;,Extra: Using index; Using filesort
      • create index idx_user_age_phone_ad on tb_user(age asc ,phone desc);
      • Extra:Using index

总结:

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

group by优化

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

如索引为idx_user_pro_age_stat,则句式可以是select ... where profession='xxx' order by age,这样也符合最左前缀法则

limit优化

常见的问题如limit 2000000, 10,此时需要 MySQL 排序前2000000条记录,但仅仅返回2000000 - 2000010的记录,其他记录丢弃,查询排序的代价非常大。
优化方案:一般分页查询时,通过创建覆盖索引能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化

例如:

-- 此语句耗时很长
select * from tb_sku limit 9000000, 10;
-- 通过覆盖索引加快速度,直接通过主键索引进行排序及查询
select id from tb_sku order by id limit 9000000, 10;
-- 下面的语句是错误的,因为 MySQL 不支持 in 里面使用 limit
-- select * from tb_sku where id in (select id from tb_sku order by id limit 9000000, 10);
-- 通过连表查询即可实现第一句的效果,并且能达到第二句的速度
select * from tb_sku as s, (select id from tb_sku order by id limit 9000000, 10) as a where s.id = a.id;
-- 数据量越大,优化越明显,上面的这组数据两种查询方式差距在1秒左右

select * from tb_sku t , (select id from tb_sku order by id limit 2000000,10) a where t.id = a.id;
select * from tb_sku limit 2000000,10;
-- 上面这组数据两种查询方式差距在0.2秒左右

count优化

MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个数,效率很高(前提是不适用where);
InnoDB 在执行 count(*) 时,需要把数据一行一行地从引擎里面读出来,然后累计计数。
优化方案:自己计数,如创建key-value表存储在内存或硬盘,或者是用redis

count的几种用法:

  • 如果count函数的参数(count里面写的那个字段)不是NULL(字段值不为NULL),累计值就加一,最后返回累计值
  • 用法:count(*)、count(主键)、count(字段)、count(1)
  • count(主键)跟count(*)一样,因为主键不能为空;count(字段)只计算字段值不为NULL的行;count(1)引擎会为每行添加一个1,然后就count这个1,返回结果也跟count(*)一样;count(null)返回0

各种用法的性能:

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

按效率排序:count(字段) < count(主键) < count(1) < count(*),所以尽量使用 count(*)

✨update优化(避免行锁升级为表锁)

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

如以下两条语句:
update student set no = '123' where id = 1;,这句由于id有主键索引,所以只会锁这一行;
update student set no = '123' where name = 'test';,这句由于name没有索引,所以会把整张表都锁住进行数据更新,解决方法是给name字段添加索引

补充:

  • 行锁:当使用行锁时,仅锁定被操作的行,不锁定其他行或整个表。这意味着其他事务仍可以访问表中的其他行,只有被锁定的行不能被其他事务修改。这种锁定方式对于高并发的读写操作非常有用,因为只有被修改的行才被锁定,其他行可以继续被访问,从而减少了冲突和等待时间。

  • 表锁:与行锁不同,当使用表锁时,整个表将被锁定,其他事务将无法访问该表,直到锁被释放。这种锁定方式在进行大量修改操作时可能会导致性能下降,因为其他事务无法访问该表,从而增加了等待时间和冲突。

总之,行锁和表锁的主要区别在于锁定的范围。行锁只锁定需要修改的行,而表锁锁定整个表。因此,当需要修改表中的一小部分数据时,最好使用行锁,而当需要修改表中的大部分或全部数据时,则最好使用表锁。

视图/存储过程/触发器

视图

  • 因为我们在创建视图的时候,指定的条件为 id<=10, id为17的数据,是不符合条件的,所以没有查询出来,但是这条数据确实是已经成功的插入到了基表中。

    • 视图的查询存在创建视图时条件的限制
  • 如果我们定义视图时,如果指定了条件,然后我们在插入、修改、删除数据时,是否可以做到必须满足条件才能操作,否则不能够操作呢? 答案是可以的,这就需要借助于视图的检查选项了。

当使用WITH CHECK OPTION子句创建视图时,MySQL会通过视图检查正在更改的每个行,例如 插入,更新,删除,以使其符合视图的定义。 MySQL允许基于另一个视图创建视图,它还会检查依赖视图中的规则以保持一致性。为了确定检查的范围,mysql提供了两个选项: CASCADED 和 LOCAL,默认值为 CASCADED 。

cascaded 级联

  • 比如,v2视图是基于v1视图的,如果在v2视图创建的时候指定了检查选项为 cascaded,但是v1视图创建时未指定检查选项。 则在执行检查时,不仅会检查v2,还会级联检查v2的关联视图v1。

    • create view v1 as select id,name from student where id <= 20;
      create view v2 as select id, name from v1 where id >= 10 with cascaded check option;
      create view v3 as select id, name from v2 where id <= 15;
      
      -- Error Code: 1369. CHECK OPTION failed 'isam.v2'
      insert into v2 values(26,'Tom');
      

local 本地

  • 比如,v2视图是基于v1视图的,如果在v2视图创建的时候指定了检查选项为 local ,但是v1视图创建时未指定检查选项。 则在执行检查时,知会检查v2,不会检查v2的关联视图v1

    • create view v1 as select id,name from student where id <= 20;
      create view v2 as select id, name from v1 where id >= 10 with local check option;
      create view v3 as select id, name from v2 where id <= 15;
      
      -- 插入成功
      insert into v2 values(26,'Tom');
      

视图的更新

  • 要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。如果视图包含以下任何一项,则该视图不可更新:

    • A. 聚合函数或窗口函数(SUM()、 MIN()、 MAX()、 COUNT()等)

    • B. DISTINCT

    • C. GROUP BY

    • D. HAVING

    • E. UNION 或者 UNION ALL

    • create view stu_v_count as select count(*) from student;
      
      -- Error Code: 1471. The target table stu_v_count of the INSERT is not insertable-into
      insert into stu_v_count values(10);
      

视图作用

  • 简单
    视图不仅可以简化用户对数据的理解,也可以简化他们的操作。那些被经常使用的查询可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件。
  • 安全
    数据库可以授权,但不能授权到数据库特定行和特定的列上。通过视图用户只能查询和修改他们所能见到的数据
  • 数据独立
    视图可帮助用户屏蔽真实表结构变化带来的影响。
  • 创建视图需要维护视图数据,但是相比直接维护底层表的数据,视图维护的数据量通常更小,因为视图只存储了查询语句的结果集,而不是整个表的数据。
  • 此外,视图可以为多个表进行联合查询,这避免了数据冗余存储和管理,减少了数据维护的工作量和风险。同时,视图的创建、修改、删除等操作通常也比直接对底层表进行操作更加简单和方便。
  • 因此,通过使用视图,可以将复杂的查询语句封装起来,简化数据访问权限、降低数据冗余、提高查询效率、简化应用程序的开发,并且减少了数据维护的工作量,这也是视图功能在数据管理中的重要作用之一。
  • 一般情况下,视图是动态计算的,也就是说,当用户查询视图时,数据库会根据视图的定义重新计算查询结果。因此,不需要使用定时任务更新视图数据。
    • **视图的数据是由底层表的数据计算得出的,当底层表的数据发生变化时,视图的数据也会相应地发生变化。**因此,无论是插入、更新、删除底层表的数据,还是查询视图,都是实时的,不需要手动更新视图数据。

存储过程

存储过程是事先经过编译并存储在数据库中的一段 SQL 语句的集合

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

特点:

  • 封装,复用 -----------------------> 可以把某一业务SQL封装在存储过程中,需要用到
    的时候直接调用即可。

  • 可以接收参数,也可以返回数据 --------> 再存储过程中,可以传递参数,也可以接收返回值。

  • 减少网络交互,效率提升 -------------> 如果涉及到多条SQL,每执行一次都是一次网络传输。 而如果封装在存储过程中,我们只需要网络交互一次可能就可以了。

  • -- 需要注意的是,在命令行中使用该语句需要用 DELIMITER $$ 更改结束符,否则报错
    DROP procedure IF EXISTS `p1`;
    
    DELIMITER $$
    CREATE PROCEDURE `p1` ()
    BEGIN
        select count(*) from student;
    END$$
    
    DELIMITER ;
    

变量

在MySQL中变量分为三种类型: 系统变量、用户定义变量、局部变量。

系统变量

系统变量 是MySQL服务器提供,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL)、会话变量(SESSION)。

  1. )查看系统变量

    SHOW [ SESSION | GLOBAL ] VARIABLES ; -- 查看所有系统变量
    SHOW [ SESSION | GLOBAL ] VARIABLES LIKE '......'; -- 可以通过LIKE模糊匹配方式查找变量
    SELECT @@[SESSION | GLOBAL] 系统变量名; -- 查看指定变量的值
    
  2. )设置系统变量

    SET [ SESSION | GLOBAL ] 系统变量名 = 值 ;
    SET @@[SESSION | GLOBAL]系统变量名 = 值 ;
    
  • 如果没有指定SESSION/GLOBAL,默认是SESSION,会话变量。
  • mysql 服务重新启动之后,所设置的全局参数会失效,要想不失效,可以在 /etc/my.cnf 中配置。
  • A. 全局变量(GLOBAL): 全局变量针对于所有的会话。
    B. 会话变量(SESSION): 会话变量针对于单个会话,在另外一个会话窗口就不生效了。

补充:

  • MySQL默认操作模式就是autocommit自动提交模式。这就表示除非显式地开始一个事务,否则每个查询都被当做一个单独的事务自动执行。我们可以通过设置autocommit的值改变是否是自动提交autocommit模式。
用户定义变量

用户定义变量 是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用 “@变量名” 使用就可以。其作用域为当前连接。

  1. 赋值
    方式一:

    SET @var_name = expr [, @var_name = expr] ... ;
    SET @var_name := expr [, @var_name := expr] ... ;
    

    赋值时,可以使用 = ,也可以使用 := 。

    方式二:

    SELECT @var_name := expr [, @var_name := expr] ... ;
    SELECT 字段名 INTO @var_name FROM 表名;
    
  2. 使用

    SELECT @var_name ;

  • 注意: 用户定义的变量无需对其进行声明或初始化,只不过获取到的值为NULL。
局部变量

局部变量 是根据需要定义的在局部生效的变量,访问之前,需要DECLARE声明。可用作存储过程内的局部变量和输入参数,局部变量的范围是在其内声明的BEGIN … END块

  1. 声明

    DECLARE 变量名 变量类型 [DEFAULT ... ] ;
    

    变量类型就是数据库字段类型:INT、BIGINT、CHAR、VARCHAR、DATE、TIME等。

  2. 赋值

    SET 变量名 = 值 ;
    SET 变量名 := 值 ;
    SELECT 字段名 INTO 变量名 FROM 表名 ... ;
    

if

if 用于做条件判断,具体的语法结构为:

IF 条件1 THEN
	.....
ELSEIF 条件2 THEN -- 可选
	.....
ELSE -- 可选
	.....
END IF;

在if条件判断的结构中,ELSE IF 结构可以有多个,也可以没有。 ELSE结构可以有,也可以没有。

参数

参数的类型,主要分为以下三种:IN、OUT、INOUT。 具体的含义如下:

类型含义备注
IN该类参数作为输入,也就是需要调用时传入值默认
OUT该类参数作为输出,也就是该参数可以作为返回值
INOUT既可以作为输入参数,也可以作为输出参数

用法:

CREATE PROCEDURE 存储过程名称 ([ IN/OUT/INOUT 参数名 参数类型 ])
BEGIN
-- SQL语句
END ;

case

case结构及作用,和我们在基础篇中所讲解的流程控制函数很类似。有两种语法格式:

  • 语法一:

    -- 含义: 当case_value的值为 when_value1时,执行statement_list1,当值为 when_value2时,执行statement_list2, 否则就执行 statement_list
    CASE case_value
        WHEN when_value1 THEN statement_list1
        [ WHEN when_value2 THEN statement_list2] ...
        [ ELSE statement_list ]
    END CASE;
    

    语法二:

    -- 含义: 当条件search_condition1成立时,执行statement_list1,当条件search_condition2成立时,执行statement_list2, 否则就执行 statement_list
    CASE
        WHEN search_condition1 THEN statement_list1
        [WHEN search_condition2 THEN statement_list2] ...
        [ELSE statement_list]
    END CASE;
    
  • 注意:如果判定条件有多个,多个条件之间,可以使用 and 或 or 进行连接。

while

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

-- 先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑
WHILE 条件 DO
    SQL逻辑...
END WHILE;

repeat

repeat是有条件的循环控制语句, 当满足until声明的条件的时候,则退出循环 。具体语法为:

-- 先执行一次逻辑,然后判定UNTIL条件是否满足,如果满足,则退出。如果不满足,则继续下一次循环
REPEAT
    SQL逻辑...
    UNTIL 条件 -- 注意这条语句结尾不加分号
END REPEAT;

loop

LOOP 实现简单的循环,如果不在SQL逻辑中增加退出循环的条件,可以用其来实现简单的死循环。

LOOP可以配合一下两个语句使用:

  • LEAVE :配合循环使用,退出循环。

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

    [begin_label:] LOOP
    SQL逻辑...
    END LOOP [end_label];
    
    LEAVE label; -- 退出指定标记的循环体
    ITERATE label; -- 直接进入下一次循环
    

    上述语法中出现的 begin_label,end_label,label 指的都是我们所自定义的标记。

游标

游标(CURSOR)是用来存储查询结果集的数据类型 , 在存储过程和函数中可以使用游标对结果集进
行循环的处理。游标的使用包括游标的声明、OPEN、FETCH 和 CLOSE,其语法分别如下。

声明游标:DECLARE 游标名称 CURSOR FOR 查询语句 ;

打开游标:OPEN 游标名称 ;

获取游标记录:FETCH 游标名称 INTO 变量 [, 变量 ]

获取游标记录:CLOSE 游标名称 ;

  • 当游标的数据集获取完毕之后,再次获取数据,就会报错,从而终止了程序的执行。需要通过MySQL中提供的 条件处理程序 Handler 来解决。

条件处理程序

条件处理程序(Handler)可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤。具体语法为:

DECLARE handler_action HANDLER FOR condition_value [, condition_value]
... statement ;

handler_action 的取值:
    CONTINUE: 继续执行当前程序
    EXIT: 终止执行当前程序
    
condition_value 的取值:
    SQLSTATE sqlstate_value: 状态码,如 02000
    SQLWARNING: 所有以01开头的SQLSTATE代码的简写
    NOT FOUND: 所有以02开头的SQLSTATE代码的简写
    SQLEXCEPTION: 所有没有被SQLWARNING 或 NOT FOUND捕获的SQLSTATE代码的简写
在MySQL中,SQLSTATE是指SQL语句执行时产生的状态码,用于表示执行结果的状态。常见的状态码及其含义如下:
	SQLSTATE值以 "02" 开头的代表 "Not found",通常用于表示未找到所请求的数据或对象。例如: 02000 表示 "No data",02001 表示 "No additional dynamic result sets returned".
	SQLSTATE值以 "01" 开头的代表 "Warning",通常用于表示一些非致命性的错误或警告信息。例如: 01000 表示 "Warning",01001 表示 "Cursor operation conflict".
	SQLSTATE值以 "42" 开头的代表 "Syntax error or access rule violation",通常用于表示SQL语句的语法错误或访问权限限制。例如: 42000 表示 "Syntax error or access rule violation".
	SQLSTATE值以 "22" 开头的代表 "Data exception",通常用于表示数据类型转换、约束违规等数据相关的异常情况。例如: 22001 表示 "String data, right truncated".
	
当执行SQL语句时出现异常情况时,MySQL会抛出异常并使用相应的SQLSTATE值来表示异常类型。SQLEXCEPTION是指所有未被SQLWARNING或NOT FOUND捕获的SQLSTATE代码,通常代表着较为严重的异常情况。

具体的错误状态码,可以参考官方文档:
https://dev.mysql.com/doc/refman/8.0/en/declare-handler.html
https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html

存储函数

存储函数是有返回值的存储过程,存储函数的参数只能是IN类型的。具体语法如下:

CREATE FUNCTION 存储函数名称 ([ 参数列表 ])
RETURNS type [characteristic ...]
BEGIN
    -- SQL语句
    RETURN ...;
END ;

characteristic说明:

  • DETERMINISTIC:相同的输入参数总是产生相同的结果
  • NO SQL :不包含 SQL 语句。
  • READS SQL DATA:包含读取数据的语句,但不包含写入数据的语句。

MySQL 存储函数是一种编写在 MySQL 数据库中的可重用代码块。它们类似于存储过程,但不像存储过程,存储函数始终返回一个值,可以在 SQL 查询中使用。

存储函数可以用于许多不同的目的,例如:

  • 数据转换:存储函数可以用来将数据从一种格式转换为另一种格式,例如将日期和时间从一种格式转换为另一种格式,或者将字符串转换为数字。
  • 数据验证:存储函数可以用于验证数据,例如验证邮件地址格式是否正确,或者验证用户名和密码是否匹配。
  • 复杂计算:存储函数可以用于执行复杂的计算,例如计算平均值、标准偏差、中位数等。
  • 数据处理:存储函数可以用于处理数据,例如提取子字符串、删除字符串中的空格、格式化数字等。

存储函数在 MySQL 中非常有用,因为它们可以提高数据库的性能和可维护性,减少代码的冗余和复杂性,并且可以在整个数据库中重复使用。

触发器

触发器是与表有关的数据库对象,指在insert/update/delete之前(BEFORE)或之后(AFTER),触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性, 日志记录 , 数据校验等操作 。

使用别名OLD和NEW来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还只支持行级触发,不支持语句级触发。

  • 这句话提到了两个概念,分别是触发器和行级触发。
    • 触发器是一种数据库对象,它可以在特定的数据库操作(例如插入、更新或删除记录)发生时自动执行一些特定的代码。可以将触发器看作是一种“事件监听器”,它在数据库发生某些特定的事件时自动触发执行。
    • 行级触发是指触发器在执行时可以引用“OLD”和“NEW”两个别名来指代发生变化的记录的“旧值”和“新值”。例如,当在数据库中更新某一行时,可以使用OLD和NEW别名来引用更新前的值和更新后的值。
    • 语句级触发是指在执行某个SQL语句时触发器只执行一次,而行级触发是指在执行SQL语句时针对每一行都会触发触发器。
触发器类型NEW 和 OLD
INSERT 型触发器NEW 表示将要或者已经新增的数据
UPDATE 型触发器OLD 表示修改之前的数据 , NEW 表示将要或已经修改后的数据
DELETE 型触发器OLD 表示将要或者已经删除的数据

语法

  1. 创建

    CREATE TRIGGER trigger_name
    BEFORE/AFTER INSERT/UPDATE/DELETE
    ON tbl_name FOR EACH ROW -- 行级触发器
    BEGIN
    	trigger_stmt ;
    END;
    
  2. 查看

    SHOW TRIGGERS ;
    
  3. 删除

    DROP TRIGGER [schema_name.]trigger_name ; -- 如果没有指定 schema_name,默认为当前数据库 。
    

✨全局锁

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

全库逻辑备份,需要加全就锁的理由

  • 对数据库进行进行逻辑备份之前,先对整个数据库加上全局锁,一旦加了全局锁之后,其他的DDL、DML全部都处于阻塞状态,但是可以执行DQL语句,也就是处于只读状态,而数据备份就是查询操作。那么数据在进行逻辑备份的过程中,数据库中的数据就是不会发生变化的,这样就保证了数据的一致性和完整性

语法

  1. 加全局锁:flush tables with read lock ;
  2. 数据备份:mysqldump -uroot –p1234 for_exercise > mydb.sql
  3. 释放锁:unlock tables ;

特点

数据库中加全局锁,是一个比较重的操作,存在以下问题:

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

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

mysqldump --single-transaction -uroot –p123456 itcast > itcast.sql

表级锁

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

  • 当一个事务获取了表级锁并在执行操作时,其他的事务如果也需要获取该表的锁,则必须等待当前事务释放锁后才能获取锁。这就导致了锁冲突的发生。

    锁冲突是由于多个事务同时请求同一资源(即同一张表)的锁而引起的。当两个或多个事务请求锁定同一张表的时候,其中一个事务会先获得锁,而其他事务则需要等待该锁释放。如果等待时间过长,就会导致性能下降。

    由于表级锁的锁定粒度大,每次操作锁住整张表,所以发生锁冲突的概率最高,并发度最低。当多个事务需要同时操作同一张表的时候,如果使用表级锁,就会导致并发度降低,从而导致性能下降。

    锁冲突会导致事务等待时间变长,从而导致性能下降。如果锁冲突严重,还可能导致死锁的发生。在MyISAM、InnoDB、BDB等存储引擎中,表级锁是一种常见的锁机制,但是在高并发场景下,一般会使用行级锁或者其他更细粒度的锁机制来避免锁冲突的发生。

对于表级锁,主要分为以下三类:表锁、元数据锁(meta data lock,MDL)、意向锁

表锁

对于表锁,分为两类:表共享读锁(read lock)、表独占写锁(write lock)

语法:

  • 加锁:lock tables 表名... read/write。
  • 释放锁:unlock tables / 客户端断开连接 。

特点:

  • 读锁:自身能够读,但不能够写(写操作会报错),不会影响其他客户端的读但会阻塞其他客户端的写。
  • 写锁:自身能够进行读写操作,并会阻塞其他客户端的读和写,

元数据锁

meta data lock , 元数据锁,简写MDL。

MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。为了避免DML与DDL冲突,保证读写的正确性。

这里的元数据,大家可以简单理解为就是一张表的表结构。 也就是说,某一张表涉及到未提交的事务时,是不能够修改这张表的表结构的。

在MySQL5.5中引入了MDL,当对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构进行变更操作的时候,加MDL写锁(排他)。

常见的SQL操作时,所添加的元数据锁:

对应SQL锁类型说明
lock tables xxx read / writeSHARED_READ_ONLY / SHARED_NO_READ_WRITE
select 、select … lock in share modeSHARED_READ与SHARED_READ、SHARED_WRITE兼容,与EXCLUSIVE互斥
insert 、update、delete、select … for updateSHARED_WRITE与SHARED_READ、SHARED_WRITE兼容,与EXCLUSIVE互斥
alter table …EXCLUSIVE与其他的MDL都互斥

两个实例各自开启事务(begin),只有先开启的实例能够update操作,另外一个实例执行该操作会被阻塞

  • 这种情况可能是因为两个MySQL实例的事务并没有共同开启,而是分别开启的。

    如果两个MySQL实例的事务都是在同一个时间点开始的,那么一个能够执行update数据操作,另一个阻塞的情况可能是由于事务隔离级别的不同。

    MySQL的事务隔离级别有四种,分别是:未提交读(Read Uncommitted)、提交读(Read Committed)、可重复读(Repeatable Read)、串行化(Serializable)。默认情况下,MySQL使用的是可重复读隔离级别。

    在可重复读隔离级别下,如果一个事务已经对某些数据行加锁,那么其他事务就不能对这些数据行进行更新操作,直到持有锁的事务释放锁为止。因此,如果先开启的事务已经对一些数据行进行了更新操作并且还没有提交事务,那么后开启的事务就会被阻塞,直到先开启的事务提交或回滚事务。

    如果你确认两个MySQL实例的事务确实是在同一个时间点开始的,并且隔离级别都是可重复读,那么可能是因为其中一个实例对某些数据行进行了写操作,而另一个实例又尝试对这些数据行进行了更新操作,导致阻塞的情况发生。

查看元数据锁情况语句:select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks ;

意向锁

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

假如没有意向锁,客户端一对表加了行锁后,客户端二如何给表加表锁呢,来通过示意图简单分析一下:

  1. 首先客户端一,开启一个事务,然后执行DML操作,在执行DML语句时,会对涉及到的行加行锁。
  2. 当客户端二,想对这张表加表锁时,会检查当前表是否有对应的行锁,如果没有,则添加表锁,此时就会从第一行数据,检查到最后一行数据,效率较低。

有了意向锁之后 :

  1. 客户端一,在执行DML操作时,会对涉及的行加行锁,同时也会对该表加上意向锁。
  2. 而其他客户端,在对这张表加表锁的时候,会根据该表上所加的意向锁来判定是否可以成功加表锁,而不用逐行判断行锁情况了。

分类

  • 意向共享锁(IS): 由语句select ... lock in share mode添加 。 与 表锁共享锁(read)兼容,与表锁排他锁(write)互斥。
  • 意向排他锁(IX): 由insert、update、delete、select…for update添加 。与表锁共
    享锁(read)及排他锁(write)都互斥,意向锁之间不会互斥。

一旦事务提交了,意向共享锁、意向排他锁,都会自动释放。

可以通过以下SQL,查看意向锁及行锁的加锁情况:

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

表中没有主键,查了个寂寞,一晚上的痛苦源泉

  • GEN_CLUST_INDEX 是 InnoDB 存储引擎中的一个特殊的聚簇索引(clustered index),它是所有 InnoDB 表必须具备的一个索引,同时也是 InnoDB 表的主键索引(primary key index)。

    具体来说,当创建一个 InnoDB 表时,如果没有为该表指定主键,则 InnoDB 存储引擎会自动创建一个名为 GEN_CLUST_INDEX 的主键索引。该索引的定义方式为:将所有非主键列和主键列一起作为索引列,按照主键列的值来排序存储数据行。

    由于 InnoDB 表必须具备主键索引,因此 GEN_CLUST_INDEX 索引对于 InnoDB 表的存储和查询非常重要。如果你在创建 InnoDB 表时指定了主键,则该主键索引会代替 GEN_CLUST_INDEX 成为主键索引。但即使如此,GEN_CLUST_INDEX 仍然会存在,并且会包含所有非主键列的值,用来支持 InnoDB 表的聚簇索引特性。

    需要注意的是,GEN_CLUST_INDEX 索引不是用户可见的索引,因此在查询表的索引时,它不会出现在索引列表中。但在 InnoDB 存储引擎内部,它扮演了非常重要的角色。

行级锁

行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在InnoDB存储引擎中。

InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。对于行级锁,主要分为以下三类:

  • 行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行update和delete。在RC、RR隔离级别下都支持。
    在这里插入图片描述

  • 间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持。
    在这里插入图片描述

  • 临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。在RR隔离级别下支持。
    在这里插入图片描述

补充:

  • 在InnoDB中,索引和数据是分离存储的,即索引和数据存储在不同的磁盘文件中。由于InnoDB使用了MVCC(多版本并发控制)的机制,因此在进行并发访问时,可能会出现多个事务同时请求对同一行进行加锁的情况。为了避免死锁的发生,在InnoDB中,锁定记录和锁定索引有所不同。

  • 锁定索引意味着锁定索引中的值,这意味着其他事务可以锁定不同的值,而不会发生冲突。例如,如果两个事务分别请求锁定id=1和id=2的行,它们可以同时执行,因为它们锁定的是不同的索引值。因此,锁定索引可以提高并发性能。

    锁定记录则需要锁定整个记录,包括主键和索引。这意味着如果有两个事务分别请求锁定同一行,它们将会冲突并被阻塞,直到其中一个事务释放锁为止。因此,锁定记录可能会导致性能问题和死锁的发生。

  • 行级锁分为行锁、间隙锁、临键锁三种类型,其中行锁既可以在索引上加锁,也可以在数据记录上加锁,因此行锁的实际类型可以是行锁索引或行锁记录。

    间隙锁和临键锁是在索引上加锁的,但它们不是行锁索引类型的锁,因为它们锁住的是一定范围内的键值之间的“间隙”,而不是某一行的具体数据记录,因此它们的实际锁类型是间隙锁类型。

    需要注意的是,间隙锁和临键锁都是行锁,但它们是一种特殊的行锁类型,它们和普通的行锁不同,因为它们不是直接锁住行数据本身,而是锁住了一定范围内的键值之间的“间隙”。

行锁

InnoDB实现了以下两种类型的行锁:

  • 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。
  • 排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。

两种行锁的兼容情况如下:

当前锁类型\请求锁类型S(共享锁)X(排他锁)
S(共享锁)兼容冲突
X(排他锁)冲突冲突

常见的SQL语句,在执行时,所加的行锁如下:

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 锁进行搜索和索引扫描,以防止幻读。

  • 针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
  • InnoDB的行锁是针对于索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,此时 就会升级为表锁。

可以通过以下SQL,查看意向锁及行锁的加锁情况:

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

间隙锁&临键锁

默认情况下,InnoDB在 REPEATABLE READ事务隔离级别运行,InnoDB使用 next-key 锁进行搜索和索引扫描,以防止幻读。

  • 索引上的等值查询(唯一索引),给不存在的记录加锁时, 优化为间隙锁 。

  • 索引上的等值查询(非唯一普通索引),向右遍历时最后一个值不满足查询需求时,next-key lock 退化为间隙锁。

    • 给最后一个满足条件的元素加临键锁,第一个不满足条件的之前元素加间隙锁

    • | lock_mode     | lock_data |
      |  X,GAP		| 8			| ---> 给8之前的元素加间隙锁
      
  • 索引上的范围查询(唯一索引)–会访问到不满足条件的第一个值为止。

    • 查询的条件为id>=19,并添加共享锁
    • 此时我们可以根据数据库表中现有的数据,将数据分为三个部
      分:[19]、(19, 25]、(25, +∞],
      • 所以数据库数据在加锁是,就是将19加了行锁,
      • 25的临键锁(包含25及25之前的间隙),
      • 正无穷的临键锁(正无穷及之前的间隙)。

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

介绍分析一下:
我们知道InnoDB的B+树索引,叶子节点是有序的双向链表。 假如,我们要根据这个二级索引查询值为18的数据,并加上共享锁,我们是只锁定18这一行就可以了吗? 并不是,因为是非唯一索引,这个结构中可能有多个18的存在,所以,在加锁时会继续往后找,找到一个不满足条件的值(当前案例中也就是29)。此时会对18加临键锁,并对29之前的间隙加锁。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值