Mysql45讲总结

99 阅读25分钟

数据库三大范式

  • 第一范式:

确保表中的每一列都是原子性的、不可再分的。例如一个字段“地址”存储了“省、市、区”的信息。应该将该列拆分成多列。

  • 第二范式:

表中所有非主属性都必须完全函数依赖整个主键

订单明细(订单ID, 产品ID, 产品名称, 产品单价, 数量),主键为 (订单ID, 产品ID),(产品名称、产品单价)只依赖于产品ID,应该将部分属性拆分新表

  • 第三范式:

消除非主属性之间的传递函数依赖

表 员工(员工ID, 姓名, 部门ID, 部门名称, 部门地点),主键为 员工ID

问题:部门名称 和 部门地点 依赖于 部门ID,而 部门ID 依赖于 员工ID。因此 部门名称 和 部门地点 传递依赖于 员工ID。这意味着同一个部门的员工信息中,部门名称和地点会被重复存储(冗余),如果部门地点变更,需要更新该部门所有员工的记录(更新异常)。

将传递依赖的非主属性(如部门名称部门地点)和它们直接依赖的属性(部门ID)拆分到一个新表中(如 部门(部门ID, 部门名称, 部门地点))。

一、mysql执行过程(第一讲)

image.png

  • 查询缓存:mysql8.0将缓存功能删除掉了,建议不要用缓存
  • 优化器:负责选择使用哪个索引、在多张表join时决定各个表的连接顺序(哪个表作为驱动表)
  • 执行器:判断用户有没有当前表权限、使用存储引擎提供的接口

二、redo log、binlog、undo log(第2讲、第23讲)

如果每次的更新操作都需要立刻写入磁盘,磁盘也需要找到记录所在位置,然后更新,IO成本很高。

MYSQL的WAL技术:Write Ahead Logging,先写日志,再写磁盘。

redo log:

当有一条数据需要更新,InnoDB引擎将记录写到redo log,然后更新内存,就算更新完成了。存储引擎会在合适时间将redo log写入磁盘。redo log保证及即使mysql异常重启,仍然保证数据不丢失,称为crash-safe。redo log记录的是物理数据页的变化,例如:在哪个数据页做了xx修改

undo log:

Undo Log 的核心作用

  1. 事务回滚(Rollback)

    • 当事务需要回滚时,InnoDB 会根据 Undo Log 中的记录,将数据恢复到事务开始前的状态。
    • 例如:执行 UPDATE 后回滚,Undo Log 会保存修改前的旧值,用于恢复。数据库崩溃前的数据恢复依靠undolog,数据库崩溃后的数据恢复依靠binlog
  2. 实现 MVCC(多版本并发控制)

    • 在 读已提交(RC)  和 可重复读(RR)  隔离级别下,Undo Log 存储数据的多个版本,使其他事务能读取到一致性快照,而不会被未提交的修改阻塞。
  3. 辅助 Crash Recovery

    • 如果 MySQL 崩溃,重启时会检查未提交的事务,并利用 Undo Log 回滚这些事务,保证数据一致性。

binlog:

原始没有innoDB引擎时,mysql是不具备crash-safe的能力,innoDB引入了redo log,引入了crash-safe能力

  • redo log是物理日志,bin log是逻辑日志,例如:给id=2的这行数据的d字段加一
  • redo log是innoDB特有,binlog是server层提供的日志能力所有引擎都可以使用
  • redo log是循环写入,文件大小是固定的;binlog可以追加写入,写满开辟新文件

举例: UPDATE SET c = c + 1 WHERE id = 2执行流程分析

深色的是server层,浅色的是innoDB引擎层,采用两阶段提交,保证逻辑日志binlog和物理日志redo log是可以match上。redolog是覆盖写入,binlog是追加写入,redolig保证磁盘数据写入正确,binlog可以用于进行数据恢复,二者必须match上,才能保证数据库崩溃时可以正确恢复数据。

image.png

三、mysql主从同步(第24到27讲)

mysql怎么保证主备一致的

image.png

主库写完binlog后,备份库与主库之间有长链接,按照备份库传过来的位置,从本地读取binlog信息传输给备份库,备份库存储relay log,然后开启多个线程,将信息更新到备份库 image.png

binlog有多种存储形式:

  • statement:存储原sql语句,DELETE t WHERE a = 1 OR b = 2 LIMIT 1;但是这样存储有问题,因为如果走a索引和b索引删除的数据是不同的,就会导致主贝不一致,例如a索引树满足条件的第一条数据是id = 1,b索引树满足条件的是id = 2,就可能导致主备删除的数据不同导致不一致。
  • row:存储被加工后的信息,需要借助工具翻译才能看懂。存储的是操作的数据的主键ID,不会出现statement的问题。
  • mixed:混合模式,由mysql自行判断使用哪种格式存储binlog,因为statmenet有时候会很节省空间

四、事务隔离级别(第三讲)

数据库的四大特性:A(原子性)C(一致性)I(隔离性)D(持久性)

隔离级别:读未提交、读已提交(解决脏读)、可重复读(解决不可重复读)、序列化(解决幻读)

  • 读未提交:一个事务未提交,将数据更新,其他事务可以读到最新的数据
  • 读已提交:一个事务未提交,更新数据,其他事务不可见;事务提交后,其他事务中可见更改的内容
  • 可重复读:一个事务提交后,其他未提交的事务读不到变化,当前事务读到读内容,与当前事务启动时读到的内容保持一致。
  • 序列化读:读数据加读锁,写数据加写锁。当事务A查询数据未提交事务,事务B是不能更新这条数据的,因为事务B需要获取写锁,但是事务A持有读锁,所以事务B需要等待

数据库的隔离级别就是通过配合MVCC实现的

MVCC实现原理:

1. 隐藏字段

InnoDB 每行记录都包含几个隐藏字段:

  • DB_TRX_ID(6字节):记录最近修改该行数据的事务ID
  • DB_ROLL_PTR(7字节):回滚指针,指向Undo Log中的旧版本数据
  • DB_ROW_ID(6字节):隐含的自增行ID(当没有主键时自动生成)

2. Undo Log

存储数据修改前的版本,形成版本链,用于:

  • 事务回滚
  • 提供MVCC的旧版本数据读取

3. ReadView

决定事务能看到哪些版本的数据,包含:

  • m_ids:生成ReadView时活跃(未提交)的事务ID列表
  • min_trx_id:m_ids中的最小值
  • max_trx_id:系统将分配给下一个事务的ID
  • creator_trx_id:创建该ReadView的事务ID

MVCC工作流程:

执行SELECT时会生成一个readView,readView里有min_trx_id,就是当前视图事务的id,使用行记录的DB_TRX_ID与当前视图的min_trx_id比较,比当前min_trx_id小的行都可以读到,否则当前数据行使用undo log找到小于min_trx_id的数据版本用于展示

举例:假设id = 1的数据的k = 1

image.png

当前隔离级别是可重复读

事务B更新完后,id = 1的数据的k = 2

事务A查询到的数据k = 1

按照可重复读隔离级别的描述,开启事务后,在当前事务读到的数据都是一致的。 事务A开启时,事务C还没有开启,所以事务A读到的k = 1,事务B虽然开启了事务,但是对id = 1数据进行了更新,叫做 【一致性读】 ,更新数据都是先读后写的,而这个读,只能读当前的值,称为“【当前读】”(currentt read),当前读是读到当前数据最新版本的值,所以B事务读到的是2,然后自增,更新成了3。除了UPDATE之外,给SELECT加锁,也是【当前读】

image.png

幻读问题

如下图所示例子,幻读是在同一个事务中多次查询一条数据,查询到的结果不同。

在可重复读隔离级别下,“快照读”是看不到别的事务插入的数据的,幻读只会在“当前读”下出现。 幻读专指新插入的行,其他事务进行的数据更新不算做幻读。

如何解决可以看锁章节的NEXT_KEY_LOCK

image.png

五、索引 【第四讲、第五讲、第十讲、第十六讲、第十七讲】###

索引的存储模型

  • 哈希表(key - value),只能等值查询,不能范围查询 image.png
  • 有序数组:支持范围查询和等值查询,但是插入效率太低,有序数组只适用于静态存储(不会修改的数据) image.png
  • n叉搜索树:如果树有k层,则树可以存储的节点为:1(k的零次方)+ k + k2(k的平方)+..+(k的n-1次方),当n=1200,树高是4,可以存储的节点总数为:1 + 1200 + 1200 * 1200 + 1200 * 1200 * 1200 = 17亿 image.png

InnoDB使用的存储模型:B+树

image.png B+树是多叉平衡搜索树,非叶子节点只存储键,叶子节点存储主键id数据,并且叶子节点间有链表,方拜年做范围查询

索引类型分为:

  • 主键索引(聚簇索引):索引叶子节点存储整行的内容,主键id作为key,整行数据作为value
  • 非主键索引(二级索引):索引叶子节点存储 当前索引字段作为key,主键id作为value

SELECT * FROM t1 WHERE k = 5,因为k非主键索引,所以需要获取到主键id后去根据主键id查询出整条数据,叫做回表

覆盖索引:

举例:select * from T where k between 3 and 5,查看这条语句的执行过程

  1. 在k索引树上找到k=3的记录,取得 ID = 300;
  2. 再到ID索引树查到ID=300对应的R3;
  3. 在k索引树取下一个值k=5,取得ID=500;
  4. 再回到ID索引树查到ID=500对应的R4;
  5. 在k索引树取下一个值k=6,不满足条件,循环结束。

在这个过程中,回到主键索引树搜索的过程,我们称为回表。可以看到,这个查询过程读了k 索引树的3条记录(步骤1、3和5),回表了两次(步骤2和4)。

如果执行下面这条sql。select ID from T where k between 3 and 5,索引树上已经有了id,无须回表,索引k已经覆盖了我们的查询需求,叫做覆盖索引

最左前缀原则:

为每一个字段都单独建立索引很浪费空间,影响数据更新、插入的效率,我们可以使用联合索引index(name,age),这个索引支持 name = xxx的等值查询,也支持 name = xxx AND age = yyy的等值查询,如果需要单独查询 age = yyy,则需要给b单独建立索引;

最左前缀原则指:联合索引的最左n个字段,或最左m个字符,都可以使用索引加速 支持name like '张%'的查询

索引下推优化:

我们建立了联合索引index(name,age)

sql:SELECT * FROM t WHERE name LIKE '张%' AND age = 10。

如果没有索引下推优化,则查询过程是:需要每条数据都回表 image.png

如果有索引下推优化,则查询过程是:当通过索引获取到多条name LIKE '张%'的数据时,先进行判断,取出索引存储的age,如果age满足 = 10,则回表,否则不回表 image.png

索引合并

索引合并是MySQL查询优化器在处理复杂查询条件时使用的一种技术。简单来说,当WHERE子句中有多个条件,并且每个条件都可以利用不同的索引时,优化器会考虑将这些索引的扫描结果合并,从而得到最终的结果集。

age和city都有索引,mysql可能会考虑做索引合并,将结果集取交集、并集

SELECT * FROM users WHERE age = 30 AND city = 'New York';
SELECT * FROM users WHERE age = 30 OR city = 'Los Angeles';

适用于多条件查询的情况

ORDER BY 原理 16、17讲

为什么将ORDER BY写在这里,因为ORDER BY也与索引有关

全字段排序

数据库表t里有name、age、city属性 image.png 假设需要查询城市为杭州的人,并且按照姓名排序取前一千人的姓名、年龄

SELECT city, name, age FROM t WHERE city = '杭州' ORDER BY name LIMIT 1000

分析一下这条sql的执行流程,explain语句中可以看到使用了city的索引,Extra属性中有using filesort说明需要排序 image.png

走city索引获取到所有city = '杭州'的数据的主键id,回表查询数据,将city、name、age放入sort_buffer中,按照name进行排序,最终取前1000条作为结果集返回 image.png

mysql会给每个线程一块空间用于排序,叫做sort_buffer,这个空间的大小由sort_buffer_size控制,如果需要排序的数据 小于 sort_buffer,则全在内存中排序,否则需要使用磁盘临时文件辅助排序。

回表将SELECT的全部字段放入sort_buffer进行排序叫做【全字段排序】,如果查询的字段过多,那么sort_buffer能容纳的数据就很少,需要使用临时文件进行辅助排序,外部排序一般使用【归并排序】,将需要排序的数据放到多个文件进行排序最后合并,排序性能会很差。

ROW_ID排序

image.png 通过设置mysql的这个属性,如果单行长度超过这个值,则就认为单行太大,换一种排序算法

image.png 只将需要排序的字段name和id放入sort_buffer中进行排序,排序后使用id回表查询其他字段。ROW_ID排序会导致多次回表查询磁盘,不会被优先选择

假如有联合索引(city,name)那么就不需要进行排序,数据天然有序

image.png

image.png

按照覆盖索引的指引,可以创建联合索引(city,name,age),那么既不需要排序,也不需要回表。explain语句中的Extra中有using index,说明使用了覆盖索引。

image.png

image.png

临时表排序

一个单词表,存储了10000个单词,需要随机取3个单词 image.png

SELECT word FROM workds ORDER BY rand() LIMIT 3

Extra中的Using temporary指需要使用临时表,Using filesort指需要排序 image.png

因为需要使用rand()的结果进行排序,行数据没有存储rand()的值,需要构建临时表,当临时表大小 小于tmp_table_size则使用内存临时表,大于的话使用磁盘临时表。

临时表存储的R是rand()的结果,W是word,因为临时表没有主键id,所以使用了pos作为主键唯一标识临时表的数据,加载到sort_buffer中,排序后,使用pos从内存临时表中获取word,返回 image.png sort_buffer进行排序时,虽然需要排序的数据比sort_buffer_size大,但是没有在磁盘开辟临时文件用于排序,使用了新的排序算法【优先队列排序】 因为只需要取排序后的结果的前三条,所以无需让10000条数据全部有序,只需要通过不断比较替换,取出前三条数据即可

image.png

ORDER BY总结:

  • 一共有两种排序方式,全字段排序、ROW_ID排序,判断单行数据大小与max_length_for_sort_data,大于的话使用ROW_ID排序,否则使用全字段排序。
  • 一共有两种排序算法:当需要排序的数据大小比sort_buffer大时,需要借助外部文件辅助排序,一般使用归并排序算法。但是虽然需要排序的数据大小比sort_buffer大,也有可能不借助外部文件,当需要的行数很少,例如LIMIT 10,会使用优先队列排序算法。
  • 在排序时,如果排序的字段在行中没有存储,需要开辟临时表进行排序

MYSQL为什么会选错索引 10讲

索引失效场景

  1. 对字段做函数操作:对字段进行函数运算后,会走全索引扫描,mysql会选择一个索引树小的索引进行扫描。mysql对于 SELECT * FROM t WHERE id + 1 = 10,也不会走索引,是mysql的问题
  2. 类型转换: select * from t_log where t_id = 1,如果t_id是字符串类型,则上述sql等价于select * from t_log where CAST(t_id AS signed int) = 1,也等于对字段做了函数操作,导致索引失效
  3. 字符编码转换: 两张表的字符集编码不同,会导致连表进行字符集转换,导致无法走索引 SELECT * FROM t1 JOIN t2 ON t1.t_id = t2.t_id

六、锁(第六讲)

全局锁:

全局锁是对整个数据库实例加锁,MySQL提供了一个加全局的方法,命令【Flush tables with read lock】(FTWRL),可以让整个库处于只读状态。

set global read only = true:FTWRL命令之后由于客户端发生异常断开,MYSQL会自动释放全局锁,readOnly数据库不会自动释放。

当需要对整个数据库做逻辑备份时会使用到。

表级锁:

MySQL里表级锁有两种:一种是表锁,一种是元数据锁(meta data lock, MDL)

表锁:

lock tables t1 read/write, 可以用unlock tables主动释放锁,也可以在客户端断开的时候自动释放锁。 lock tables除了会限制别的线程读写,也限定本线程接下的操作对象。

lock tables t1 read, t2 write

MDL:

MDL不需要显示使用,当数据库引擎不支持行锁时,在访问一个表的时候会自动加上。当对一个表做增删改查,加MDL读锁;当对一个表结构做变更的时候,加MDL写锁。

读锁之间不排斥,可以多线程对同一张表增删改查

读写锁、写锁之间是互斥的,用来变更表结构操作的安全性

行锁:

MYSQL的行锁是由各个引擎实现的,MYISAM就不支持行锁。

image.png 事务A持有了两条数据的行锁,事务B会被阻塞。在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议

间隙锁(第20讲)###

什么是间隙锁: SELECT * FROM t WHERE c = 5 FOR UPDATE。5这条数据不存在,所以行锁无法锁住这条数据,导致了其他事务可以进行插入操作,引出幻读问题。所以Innodb引入了间隙锁,锁住行与行之间的间隙。

行锁的冲突关系如下: image.png

间隙锁之间是没有冲突的,与间隙锁有冲突的是“往这个间隙插入一条数据”的操作。举例:c=7的数据不存在的话,两个事务都可以正常加锁,没有冲突。 image.png

间隙锁和行锁合称【NEXT_KEY_LOCK】,一般给行加锁都是加NEXT_KEY_LOCK,会退化成间隙锁或行锁。

  1. 对于纯粹的当前读操作(SELECT ... FOR UPDATE/LOCK IN SHARE MODEUPDATEDELETE):  Next-Key Lock 机制在可重复读隔离级别下可以完全防止幻读。它通过锁定记录和间隙,有效阻止了其他事务插入可能导致幻读的新记录。
  2. 对于纯粹的、不加锁的快照读操作(普通 SELECT):  MVCC 机制本身在可重复读隔离级别下可以完全防止幻读,读取的是事务开始时的一致性快照。
  3. 关键限制:  如果同一个事务内部混合使用了快照读和当前读(或写操作) ,并且当前读/写的范围覆盖了之前快照读的范围,那么无法完全防止观察到幻读现象。这是因为快照读不阻塞插入,而后续的当前读会看到已经提交的、在快照读之后插入的数据。例如:先不加锁快照读查询数据,其他事务插入数据后,又使用当前读查询数据,会有幻读问题。

间隙锁的引入也可能引发死锁问题:两个间隙锁不互斥,但是写入数据会造成死锁。 image.png

加锁规则(第21讲)###

原则1:加锁的基本单位是next-key-lock, 前开后闭区间。

原则2:查找过程中访问到的数据才会加锁。

优化1:索引的等值查询,给唯一索引加锁,next-key-lock退化成行锁。

优化2:索引的等值查询,向右遍历到最后一条数据不满足条件,next-key-lock退化成间隙锁。

一个bug:唯一索引的范围查询,会访问到不满足条件的值为止。

七、排查查询语句被阻塞或者缓慢的问题(第十九讲)

可以从表锁、行锁、一致性读等方案考虑,并结合使用SHOW PROCESSLIST等命令结合排查

case1: 表锁:A会话线程将t表加了写锁,其他线程无法进行读操作

image.png 使用SHOW PROCESSLIST 命令查看

image.png

case2 行锁:

B事务想加读锁,A事务给这条数据加了写锁,拿不到行锁,所以被阻塞

image.png

case3 一致性读 查询慢: image.png

查询 SELECT * FROM t WHERE id = 1 很慢,需要800ms

查询 SELECT * FROM t WHERE id = 1 LOCK IN SHARE MODE 很快

image.png

带lock in share mode的SQL语句,是当前读,因此会直接读到1000001这个结果,所以速度很

快;而select *fromt where id=1这个语句,是一致性读,因此需要从1000001开始,依次执行

undo log,执行了100万次以后,才将1这个结果返回。

参考文章:juejin.cn/post/734425…

八、到底能否使用join(第34、35讲)

九、mysql的执行顺序

在 MySQL 中,SQL 语句的执行顺序可能与你在 SQL 查询中编写的顺序不同。这是因为 MySQL 的查询优化器会根据统计信息和查询的复杂度来决定最优的执行计划。然而,了解 SQL 语句的逻辑执行顺序对于编写高效和可维护的查询是非常有帮助的。下面是一个标准的 SQL 语句的逻辑执行顺序,尽管实际的执行计划可能会有所不同:

  1. FROM 子句:首先,MySQL 从数据库中读取数据。这是通过 FROM 子句指定的表或视图开始的。如果有 JOIN 操作,这些 JOIN 也会在这个阶段处理。
  2. WHERE 子句:接下来,MySQL 会过滤不符合 WHERE 子句条件的行。这是在读取数据后立即进行的,以减少需要进一步处理的行数。
  3. GROUP BY 子句:如果查询中包含 GROUP BY 子句,MySQL 会根据指定的列或表达式对结果集进行分组。这通常在聚合函数(如 SUM、AVG、COUNT 等)之前执行。
  4. HAVING 子句:HAVING 子句用于过滤 GROUP BY 产生的分组。这与 WHERE 子句不同,WHERE 是在数据分组之前过滤行,而 HAVING 是在数据分组之后过滤分组。
  5. SELECT 子句:选择指定的列或计算结果。在 HAVING 过滤后,MySQL 选择输出哪些列或计算哪些表达式。
  6. DISTINCT 关键字:如果使用了 DISTINCT 关键字,MySQL 会移除结果中的重复行。
  7. ORDER BY 子句:对结果集进行排序。这是在所有其他处理完成后进行的,确保最终结果按照指定的顺序显示。
  8. LIMIT 和 OFFSET 子句:最后,LIMIT 和 OFFSET 子句用于限制返回的行数或跳过一定数量的行。

十、explain

explain的type列:

1. system

  • 含义:这是 const 类型的一个特例。表只有一行数据(等同于系统表)。这是性能最高的类型。

  • 出现场景:MyISAM 或 Memory 引擎的表,只有一条数据。

    sql

    -- 例如,一个只有一行数据的系统配置表
    EXPLAIN SELECT * FROM system_config_table;
    

2. const

  • 含义:通过索引一次就能找到唯一的一行数据。它用于查询 PRIMARY KEY 或 UNIQUE INDEX 的所有列与常量值进行比较的情况。因为只匹配一行,所以速度非常快。

  • 出现场景:对主键或唯一索引的等值查询。

    sql

    EXPLAIN SELECT * FROM users WHERE id = 1;
    -- id 是主键
    

3. eq_ref

  • 含义:在表连接时,对于来自前一个表的每一行,从当前表中读取唯一的一行。这是除了 system 和 const 之外最好的连接类型。它通常出现在使用 PRIMARY KEY 或 UNIQUE NOT NULL INDEX 作为连接条件的查询中。

  • 出现场景:多表连接查询,其中驱动表的每一行都唯一对应被驱动表的一行。常见于 A JOIN B ON A.id = B.id 且 B.id 是主键或唯一索引。

    sql

    EXPLAIN SELECT *
    FROM orders
    JOIN customers ON orders.customer_id = customers.id;
    -- customers.id 是主键,对于 orders 表中的每一行 customer_id,都只对应 customers 表中的一行
    

4. ref

  • 含义:使用非唯一性索引进行查找,返回匹配某个单个值的所有行。因为索引是非唯一的,所以可能会找到多行,它比 eq_ref 差,但依然是一个很好的类型。

  • 出现场景:使用非唯一索引的等值查询,或者连接条件使用了非唯一索引。

    sql

    EXPLAIN SELECT * FROM users WHERE last_name = 'Smith';
    -- 假设 last_name 字段上有一个普通索引 (非唯一),那么所有姓 Smith 的人都会被找到
    
    EXPLAIN SELECT *
    FROM orders o
    JOIN products p ON o.product_id = p.id;
    -- 如果 p.id 不是主键/唯一索引,而是一个普通索引,那么连接类型可能就是 ref
    

5. ref_or_null

  • 含义:类似于 ref,但是 MySQL 会额外搜索包含 NULL 值的行。这种类型通常出现在涉及 IS NULL 条件的子查询中。

  • 出现场景:对某个索引列进行等值比较 OR 该列是否为 NULL。

    sql

    EXPLAIN SELECT * FROM users WHERE last_name = 'Smith' OR last_name IS NULL;
    -- last_name 列有索引
    

6. range

  • 含义:只检索给定范围内的行,使用索引来选择行。比全索引扫描 (index) 要好,因为它不需要扫描全部索引,只扫描范围内的部分。

  • 出现场景:在索引列上使用范围操作符,如 BETWEEN><>=<=IN()LIKE ‘pattern%’(前缀匹配)。

    sql

    EXPLAIN SELECT * FROM users WHERE age BETWEEN 20 AND 30;
    EXPLAIN SELECT * FROM users WHERE id IN (1, 5, 10);
    EXPLAIN SELECT * FROM users WHERE last_name LIKE 'Smi%';
    -- age, id, last_name 列上需要有索引
    

7. index

  • 含义全索引扫描(Full Index Scan)。MySQL 遍历整个索引树来查找数据。

    • 与 ALL 全表扫描的区别:index 只扫描索引文件(通常比数据文件小),而 ALL 是扫描所有数据行。

    • 两种情况会出现:

      1. 覆盖索引 (Covering Index) :查询的字段全部包含在某个索引中。这时,MySQL 只需要读取索引,而无需回表查数据行,虽然扫描了整个索引,但有时反而比随机磁盘读取(如 ref)更快。
      2. 全表扫描需要按索引的顺序来执行,以避免排序。
  • 出现场景

    sql

    -- 情况1:覆盖索引
    -- 假设在 (last_name, first_name) 上有一个联合索引
    EXPLAIN SELECT last_name, first_name FROM users;
    -- 所需数据都在索引里,所以会进行全索引扫描
    
    -- 情况2:使用索引进行排序,但WHERE条件无法使用索引
    EXPLAIN SELECT * FROM users ORDER BY id;
    -- 如果WHERE条件无法过滤任何数据,但ORDER BY的id是主键,优化器可能会选择按索引顺序扫描整个表
    

8. ALL

  • 含义全表扫描(Full Table Scan)。MySQL 会读取表中的每一行来找到匹配的行。这是性能最差的类型,必须极力避免,尤其是在大表上。

  • 出现场景:查询没有使用索引,或者索引选择性极差导致优化器认为全表扫描更快。

    sql

    EXPLAIN SELECT * FROM users WHERE country = 'Ghana';
    -- 如果 country 字段上没有索引,或者表中几乎所有国家都是 'Ghana',优化器就会选择全表扫描
    

十一、Mysql主从复制延迟如何解决

  1. 保证主库与从库机器性能一致,保证主库同步过来的binlog从库可以短时间内消费完成
  2. 一主多从:如果需要在从库跑大量后台sql,单独部署机器运行,避免影响从库消费binlog
  3. 主库的事务必须执行完成才会写入binlog将数据同步到从库,避免大事务
  4. 在业务低峰期进行刷数、清理数据等大量写操作
  5. mysql从库会开启多线程并行消费binlog