八股(6)——MySQL(概念相关)
- 4.2 MySQL
- MySQL 基础
- 关系型数据库介绍
- ACID
- MySQL 介绍
- MySQL 基础架构
- MySQL 存储引擎
- MySQL 存储引擎架构了解吗?
- MySQL 支持哪些存储引擎?默认使用哪个?
- MySQL索引
- 什么是索引?
- MySQL索引的底层数据结构?(B+树)
- 什么是聚簇索引(聚集索引)什么是非聚簇索引 ?
- 什么是回表查询?
- 什么是覆盖索引?
- 超大分页处理
- 如何创建索引
- 创建索引的原则
- 什么情况下索引会失效 ?
- 谈一谈你对sql的优化的经验
- MySQL 事务
- 何谓事务?
- 何谓数据库事务?
- 并发事务带来了哪些问题?
- 如何解决这些并发问题——事务隔离级别
- MySQL 的隔离级别是基于锁实现的吗?
- MySQL 锁
- 表级锁和行级锁了解吗?有什么区别?
- 行级锁的使用有什么注意事项?
- 共享锁和排他锁呢?
- 意向锁有什么作用?
- InnoDB 有哪几类行锁?
- 当前读和快照读有什么区别?
主要来自javaguide,加上自己的理解,放这里是方便自己时不时打开看看,后续每一次看的时候应该都会逐渐换成自己最新的理解。
4.2 MySQL
MySQL 基础
关系型数据库介绍
是一种建立在关系模型的基础上的数据库。关系模型表明了数据库中所存储的数据之间的联系(一对一、一对多、多对多)。
关系型数据库中,我们的数据都被存放在了各种表中(比如用户表)。
数据存储在固定的行和列结构中,每一行代表一个记录,每一列代表记录的一个属性。
大部分关系型数据库都使用 SQL 来操作数据库中的数据。并且,大部分关系型数据库都支持事务的四大特性(ACID——原子性( Atomicity )、一致性( Consistency )、隔离性( Isolation )和持续性( Durability ))。
有哪些常见的关系型数据库呢?
MySQL、PostgreSQL、Oracle、SQL Server、SQLite(微信本地的聊天记录的存储就是用的 SQLite)
ACID
-
原子性(Atomicity)
- 定义:原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部执行,要么全部不执行。
- 示例:以银行转账为例,从账户A转账100元到账户B,这个事务包含两个操作:从账户A扣除100元,在账户B增加100元。原子性要求这两个操作必须作为一个整体来执行,不能出现只完成了其中一个操作的情况。如果在执行过程中出现错误,比如账户A余额不足或者系统出现故障,整个转账事务就会回滚,账户A和账户B的余额都不会改变,就好像这个转账操作从来没有发生过一样。
-
一致性(Consistency)
- 定义:事务必须使数据库从一个一致性状态变换到另一个一致性状态。也就是说,在事务开始之前和结束之后,数据库都必须满足一定的完整性约束条件。
- 示例:在一个包含学生成绩管理的数据库中,有一个完整性约束是学生的成绩必须在0 - 100分之间。当执行一个更新学生成绩的事务时,比如将某个学生的成绩从80分更新为90分,在事务完成后,数据库仍然要满足成绩的取值范围这个约束条件。如果事务中的操作导致违反了这个约束(如将成绩更新为120分),那么数据库就会拒绝这个事务,以保持数据的一致性。
-
隔离性(Isolation)
- 定义:多个事务并发执行时,一个事务的执行不能被其他事务干扰,各个事务之间要相互隔离。
- 示例:假设有两个事务T1和T2同时对同一个数据库中的数据进行操作。T1是查询学生的平均成绩,T2是更新某个学生的成绩。隔离性要求T1在查询平均成绩时,不能受到T2更新成绩操作的影响,即T1应该查询到在T2操作之前或者之后稳定的平均成绩,而不是一个处于中间状态、受到T2干扰的数据。这是通过数据库的隔离级别来控制的,不同的隔离级别会对并发事务之间的相互影响程度做出不同的规定。
-
持久性(Durability)
- 定义:持久性是指一旦事务提交,它对数据库中数据的改变就应该是永久性的。即使数据库系统遇到故障(如断电、系统崩溃等),已经提交的事务的数据修改也不会丢失。
- 示例:当一个事务完成了将新的订单信息插入到电商数据库中并提交后,这些订单信息就应该持久地存储在数据库中。如果数据库所在的服务器突然断电,在系统恢复后,之前提交的订单信息仍然应该能够被查询到,这是通过数据库的日志机制、数据备份和恢复等技术来实现的。
MySQL 介绍
MySQL 是一种关系型数据库,主要用于持久化存储我们的系统中的一些数据比如用户信息。
MySQL 开源免费且较成熟,被大量使用在各种系统中。
任何人都可以在 GPL(General Public License) 的许可下下载并根据个性化的需要对其进行修改。
MySQL 的默认端口号是3306。
MySQL 基础架构
从上图可以看出, MySQL 主要由下面几部分构成:
- 连接器: 身份认证和权限相关(登录 MySQL 的时候)。
- 查询缓存: 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,不太实用)。
- 分析器: 先看 SQL 语句要干嘛,再检查 SQL 语句语法是否正确。
- 优化器: 按照 MySQL 认为最优的方案去执行。
- 执行器: 执行语句,然后从存储引擎返回数据。 执行语句之前会先判断是否有权限,如果没有权限的话,就会报错。
- 插件式存储引擎 : 主要负责数据的存储和读取,采用的是插件式架构,支持 InnoDB、MyISAM、Memory 等多种存储引擎。
MySQL 存储引擎
MySQL 核心在于存储引擎,想要深入学习 MySQL,必定要深入研究 MySQL 存储引擎。
MySQL 存储引擎架构了解吗?
存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的,而不是基于库的,所以存储引擎也可被称为表类型。
MySQL 存储引擎采用的是插件式架构,支持多种存储引擎,我们甚至可以为不同的数据库表设置不同的存储引擎以适应不同场景的需要。
存储引擎是基于表的,而不是数据库。
并且,还可以根据 MySQL 定义的存储引擎实现标准接口来编写一个属于自己的存储引擎。这些非官方提供的存储引擎可以称为第三方存储引擎,区别于官方存储引擎。(像目前最常用的 InnoDB 其实刚开始就是一个第三方存储引擎,后面由于过于优秀,其被 Oracle 直接收购了。)
MySQL 支持哪些存储引擎?默认使用哪个?
MySQL 支持多种存储引擎,可以通过 show engines
命令来查看 MySQL 支持的所有存储引擎。
从上图我们可以查看出, MySQL 当前默认的存储引擎是 InnoDB。并且,所有的存储引擎中只有 InnoDB 是事务性存储引擎,也就是说只有 InnoDB 支持事务。(不同的 MySQL 版本之间可能会有差别)
在mysql中提供了很多的存储引擎,比较常见有InnoDB、MyISAM、Memory:
- InnoDB存储引擎是mysql5.5之后是默认的引擎,它支持事务、外键、表级锁和行级锁
- MyISAM是早期的引擎,它不支持事务、只有表级锁、也没有外键,用的不多
- Memory主要把数据存储在内存,支持表级锁,没有外键和事务,用的也不多
MySQL索引
什么是索引?
- 定义:索引是一种数据结构,用于帮助数据库系统快速地定位和访问表中的数据。它就像是一本书的目录,通过索引,数据库可以快速地找到需要的数据行,而不必遍历整个数据表。
- 作用:主要是加快数据检索速度。在没有索引的情况下,如果要查找满足特定条件的数据,数据库可能需要逐行扫描整个数据表。
例如,在一个包含数百万条用户记录的表中,要查找所有年龄大于 30 岁的用户,如果没有索引,数据库可能需要检查每一条记录的年龄字段。而有了索引,数据库可以直接定位到年龄字段大于 30 岁的记录所在的位置,大大提高了查询效率。
MySQL索引的底层数据结构?(B+树)
查找都不快速,数据量很大时,树的层级太高了——解决方法是 B树:
相比B树:
- 磁盘读写代价B+树更低
- B+树的非叶子节点只存储指针,不存储数据,叶子节点才会真正存储数据
- 上面这些非叶子节点只是起导航的作用,所以在查找到叶子节点时,不会像B树一样要读取路径上的数据
- 查询效率B+树更加稳定:所有元素都会出现在叶子节点(比如根节点的38在叶子节点也出现了),所以每一次查询的路径都类似(从根节点一步步往下),所以稳定。
- B+树便于扫库和区间查询
- 扫库:B + 树的叶子节点形成了一个有序的链表。在进行全表扫描(扫库)时,数据库系统可以从链表的头部开始,依次遍历所有的叶子节点,从而获取到表中的所有数据记录。
- 区间查询:一旦找到区间下限对应的叶子节点,由于叶子节点之间是顺序链接的,就可以沿着链表顺序遍历叶子节点,直到找到区间上限对应的节点。
什么是聚簇索引(聚集索引)什么是非聚簇索引 ?
单独给字段创建的索引大部分就是二级索引。
什么是回表查询?
如果是查询下面这条语句,看动图:
什么是覆盖索引?
查询可以直接从索引中获取所需的数据,而无需再去访问数据表中的行记录。
超大分页处理
如何创建索引
- 在MySQL中创建索引的基本语法
- 创建普通索引: 可以使用
CREATE INDEX
语句来创建普通索引。语法如下:CREATE INDEX index_name ON table_name (column_name);
- 创建唯一索引: 使用
CREATE UNIQUE INDEX
语句来创建唯一索引。唯一索引确保索引列中的值是唯一的,比如用户的id名必须唯一(允许为NULL,但NULL值也只能出现一次)。语法如下:CREATE UNIQUE INDEX index_name ON table_name (column_name);
- 创建主键索引:当定义表的时候,可以直接指定主键,数据库会自动为主键列创建主键索引。例如:
在这个例子中,CREATE TABLE students ( student_id INT PRIMARY KEY, student_name VARCHAR(50), age INT );
student_id
列被定义为主键,数据库会自动为其创建主键索引。如果要在已经存在的表中添加主键索引(假设表中有合适的列可以作为主键),可以使用ALTER TABLE
语句。例如,将employees
表中的employee_id
列设置为主键并创建主键索引:ALTER TABLE employees ADD CONSTRAINT pk_employee_id PRIMARY KEY (employee_id);
- 创建组合索引:组合索引是基于多个列创建的索引。语法如下:
创建组合索引时要注意列的顺序。一般来说,应该把在查询中最常使用且选择性高(列中的不同值数量较多)的列放在前面,这样可以更好地发挥组合索引的作用。CREATE INDEX index_name ON table_name (column1, column2,...);
- 创建普通索引: 可以使用
创建索引的原则
什么情况下索引会失效 ?
- 违反最左前缀法则(前提:使用了复合索引,或者说联合索引)
- 范围查询右边的列,不能使用索引
- 不要在索引列上进行运算操作, 索引将失效(比如substring)
- 字符串不加单引号,造成索引失效。(类型转换)
- 以%开头的Like模糊查询,索引失效
谈一谈你对sql的优化的经验
- 表的设计优化,数据类型的选择
- 索引优化,索引创建原则
- sql语句优化,避免索引失效,避免使用select * ….
- 主从复制、读写分离,不让数据的写入,影响读操作
- 分库分表
- 主从复制是一种数据库技术,用于在一个主数据库(Master)和一个或多个从数据库(Slave)之间复制数据。在主数据库上发生的所有写操作(如插入、更新、删除)都会以某种方式(通常是基于二进制日志,即
Binlog)记录下来,然后从数据库会读取这些日志,并将其中记录的操作在自己的数据库中重新执行,从而保持与主数据库的数据同步。- 读写分离是基于主从复制的一种数据库架构策略。其核心思想是将数据库的读操作和写操作分离开来,让主数据库只负责处理写操作(如插入、更新、删除),从数据库负责处理读操作(如查询)。应用程序通过某种方式(通常是使用数据库中间件)来区分读写操作,并将它们路由到不同的数据库实例上。
MySQL 事务
何谓事务?
一言蔽之,事务是一组操作的集合,是一个不可分割的工作单位,要么都执行,要么都不执行。
事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账 1000 元,这个转账会涉及到两个关键操作,这两个操作必须都成功或者都失败。
- 将小明的余额减少 1000 元
- 将小红的余额增加 1000 元。
事务会把这两个操作就可以看成逻辑上的一个整体,这个整体包含的操作要么都成功,要么都要失败。这样就不会出现小明余额减少而小红的余额却并没有增加的情况。
何谓数据库事务?
大多数情况下,我们在谈论事务的时候,如果没有特指分布式事务,往往指的就是数据库事务。
数据库事务在我们日常开发中接触的最多了。如果你的项目属于单体架构的话,你接触到的往往就是数据库事务了。
那数据库事务有什么作用呢?
简单来说,数据库事务可以保证多个对数据库的操作(也就是 SQL 语句)构成一个逻辑上的整体。构成这个逻辑上的整体的这些数据库操作遵循:要么全部执行成功,要么全部不执行 。只要有一个sql失败了,那就需要回滚事务。
# 开启一个事务
START TRANSACTION;
# 多条 SQL 语句
SQL1,SQL2...
### 提交事务
COMMIT;
另外,关系型数据库(例如:MySQL
、SQL Server
、Oracle
等)事务都有 ACID 特性:
- 原子性(
Atomicity
) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用; - 一致性(
Consistency
): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;或者说是满足一定的约束,比如学生成绩修改要满足≤总分。这个是最终的目的! - 隔离性(
Isolation
): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的; - 持久性(
Durabilily
): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
🌈 这里要额外补充一点:只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。也就是说 A、I、D 是手段,C 是目的!
并发事务带来了哪些问题?
在典型的应用程序中,多个事务并发运行,经常会有多个用户对同一数据进行操作的情况。并发虽然是必须的,但可能会导致并发事务问题:脏读、不可重复读、幻读。
如何解决这些并发问题——事务隔离级别
SQL 标准定义了四个隔离级别:
- 读未提交 : 最低的隔离级别,允许读取尚未提交的数据变更,三种并发问题都有。
- 读已提交 : 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
- 可重复读 : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- 串行化 : 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,几乎放弃了并发,这样事务之间就完全不可能产生干扰。
总结: 最低的隔离级别是读未提交,它无法解决任何一种问题。然后为了解决脏读,就使用读已提交,所以事务B只能读取到事务A提交以后的数据了。但是仍然会出现不可重复读的问题,也就是事务A两次查询的结果不一致,所以要使用可重复读解决这个问题。但是此时仍然有幻读的问题,所以需要最高的隔离级别:串行化,它会一次逐个执行所有的事务,即放弃了并发。
MySQL 的隔离级别是基于锁实现的吗?
MySQL 的隔离级别基于锁和 MVCC 机制共同实现的。
串行化隔离级别,是通过锁来实现的。其他的隔离级别都是基于 MVCC 实现。
不过, 串行化之外的其他隔离级别可能也需要用到锁机制,就比如可重复读在当前读情况下需要使用加锁读来保证不会出现幻读。
MySQL 锁
表级锁和行级锁了解吗?有什么区别?
MyISAM 仅仅支持表级锁(table-level locking),一锁就锁整张表,这在并发写的情况下性非常差。
InnoDB 不光支持表级锁(table-level locking),还支持行级锁(row-level locking),默认为行级锁。行级锁的粒度更小,仅对相关的记录上锁即可(对一行或者多行记录加锁),所以对于并发写入操作来说, InnoDB 的性能更高。
表级锁和行级锁对比 :
- 表级锁: MySQL 中锁定粒度最大的一种锁,是针对非索引字段加的锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM 和 InnoDB 引擎都支持表级锁。
- 行级锁: MySQL 中锁定粒度最小的一种锁,是针对索引字段加的锁,只针对当前操作的行记录进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。
行级锁的使用有什么注意事项?
InnoDB 的行锁是针对索引字段加的锁,表级锁是针对非索引字段加的锁。当我们执行 UPDATE
、DELETE
语句时,如果 WHERE
条件中字段没有命中唯一索引或者索引失效的话,就会导致扫描全表对表中的所有行记录进行加锁。这个在我们日常工作开发中经常会遇到,一定要多多注意!!!
- 行锁原理基于索引:InnoDB 存储引擎的行锁是通过索引来实现精准锁定的。当执行UPDATE或DELETE操作时,如果WHERE条件中的字段有合适的索引(比如唯一索引或者普通索引),数据库可以利用索引快速定位到需要操作的行,然后只对这些行加行锁。
例如,在一个users表中,对user_id(假设user_id是唯一索引)执行UPDATE users SET password = ‘new_password’ WHERE user_id = 123操作时,InnoDB 会根据user_id索引快速找到user_id =123的行,然后只对这一行加行锁,其他行不受影响,这样可以实现高度的并发操作,因为不同行的更新可以同时进行。- 索引失效导致行锁升级为表锁:如果WHERE条件中的字段没有命中唯一索引或者索引因为某些原因失效(如数据类型隐式转换、对索引列进行函数操作等情况),数据库在执行UPDATE或DELETE操作时,无法通过索引精准定位行,就会退而求其次采用表级锁。
例如,在users表中,如果执行UPDATE users SET status = ‘inactive’ WHERE age + 10 > 30(这里对age列进行了运算,导致索引失效),InnoDB会对users表的所有行加锁,在这个锁释放之前,其他任何对users表的UPDATE或DELETE操作(即使是操作其他行)都需要等待,这会严重影响数据库的并发性能。
不过,很多时候即使用了索引也有可能会走全表扫描,这是因为 MySQL 优化器的原因。
共享锁和排他锁呢?
不论是表级锁还是行级锁,都存在共享锁(Share Lock,S 锁)和排他锁(Exclusive Lock,X 锁)这两类:
- 共享锁(S 锁) :又称读锁,事务在读取记录的时候获取共享锁,允许多个事务同时获取(锁兼容)。
- 排他锁(X 锁) :又称写锁/独占锁,事务在修改记录的时候获取排他锁,不允许多个事务同时获取。如果一个记录已经被加了排他锁,那其他事务不能再对这条事务加任何类型的锁(锁不兼容)。
排他锁与任何的锁都不兼容,共享锁仅和共享锁兼容。
S 锁 | X 锁 | |
---|---|---|
S 锁 | 不冲突 | 冲突 |
X 锁 | 冲突 | 冲突 |
由于 MVCC 的存在,对于一般的 SELECT
语句,InnoDB 不会加任何锁。不过, 你可以通过以下语句显式加共享锁或排他锁。
# 共享锁
SELECT ... LOCK IN SHARE MODE;
# 排他锁
SELECT ... FOR UPDATE;
意向锁有什么作用?
如果需要用到表锁的话,如何判断表中的记录没有行锁呢?一行一行遍历肯定是不行,性能太差。我们需要用到一个叫做意向锁的东西来快速判断是否可以对某个表使用表锁。
意向锁是表级锁,共有两种:
- 意向共享锁(Intention Shared Lock,IS 锁):事务有意向对表中的某些加共享锁(S 锁),加共享锁前必须先取得该表的 IS 锁。
- 意向排他锁(Intention Exclusive Lock,IX 锁):事务有意向对表中的某些记录加排他锁(X 锁),加排他锁之前必须先取得该表的 IX 锁。
意向锁是有数据引擎自己维护的,用户无法手动操作意向锁,在为数据行加共享 / 排他锁之前,InooDB 会先获取该数据行所在在数据表的对应意向锁。
意向锁之间是互相兼容的。
IS 锁 | IX 锁 | |
---|---|---|
IS 锁 | 兼容 | 兼容 |
IX 锁 | 兼容 | 兼容 |
意向锁和共享锁和排它锁互斥(这里指的是表级别的共享锁和排他锁,意向锁不会与行级的共享锁和排他锁互斥)。
IS 锁 | IX 锁 | |
---|---|---|
S 锁 | 兼容 | 互斥 |
X 锁 | 互斥 | 互斥 |
InnoDB 有哪几类行锁?
MySQL InnoDB 支持三种行锁定方式:
- 记录锁(Record Lock) :也被称为记录锁,属于单个行记录上的锁。
- 间隙锁(Gap Lock) :锁定一个范围,不包括记录本身。
- 临键锁(Next-key Lock) :Record Lock+Gap Lock,锁定一个范围,包含记录本身。记录锁只能锁住已经存在的记录,为了避免插入新记录,需要依赖间隙锁。
InnoDB 的默认隔离级别 RR(可重读)是可以解决幻读问题发生的,主要有下面两种情况:
- 快照读(一致性非锁定读) :由 MVCC 机制来保证不出现幻读。
- 当前读 (一致性锁定读): 使用 Next-Key Lock 进行加锁来保证不出现幻读。
当前读和快照读有什么区别?
- 当前读:当前读是一种读取数据的方式,它读取的是数据库当前最新的数据状态。在执行当前读操作时,会对读取的数据加锁,以确保读取的数据是最新的且在读取过程中不会被其他事务修改。
例如,在执行SELECT… FOR UPDATE(对读取的行加排他锁,用于后续可能的更新操作)或SELECT… LOCK IN SHARE MODE(对读取的行加共享锁,用于保证读取的数据不被其他事务排他性地修改)这些 SQL 语句,以及UPDATE、DELETE操作时,都会进行当前读。 - 快照读:快照读读取的是事务开始时(更准确地说是在事务开始时创建的 Read View 所对应的版本)的数据版本。它不需要对读取的数据进行加锁,通过多版本并发控制(MVCC)机制来提供数据的一致性读取。在一个事务中,多次进行快照读,只要这个事务还没有结束,读取到的数据版本是相同的(在可重复读隔离级别下)。
例如,普通的SELECT语句(没有使用FOR UPDATE或LOCK IN SHARE MODE)在 InnoDB 存储引擎下通常是快照读。
快照即记录的历史版本,每行记录可能存在多个历史版本(多版本技术)。
快照读的情况下,如果读取的记录正在执行 UPDATE/DELETE 操作,读取操作不会因此去等待记录上 X 锁的释放,而是会去读取行的一个快照。
只有在事务隔离级别 RC(读取已提交) 和 RR(可重读)下,InnoDB 才会使用一致性非锁定读:
- 在 RC 级别下,对于快照数据,一致性非锁定读总是读取被锁定行的最新一份快照数据。
- 在 RR 级别下,对于快照数据,一致性非锁定读总是读取本事务开始时的行数据版本。
快照读比较适合对于数据一致性要求不是特别高且追求极致性能的业务场景。
当前读 (一致性锁定读)就是给行记录加 X 锁或 S 锁。
当前读的一些常见 SQL 语句类型如下:
# 对读的记录加一个X锁
SELECT...FOR UPDATE
# 对读的记录加一个S锁
SELECT...LOCK IN SHARE MODE
# 对修改的记录加一个X锁
INSERT...
UPDATE...
DELETE...