博主个人介绍
辛苦牛,掌握主流技术栈,包括前端后端,已经7年时间,曾在税务机关从事开发工作,目前在国企任职。希望通过自己的不断分享,可以帮助各位想或者已经走在这条路上的朋友一定的帮助
❤️金九银十马上就要来啦,各位小伙伴们有计划跳槽的要开始准备了,博主接下来一段时间会给大家持续更新面试题目,大家持续关注一下,感谢
之前有写过一篇mysql优化的文章,链接放给大家,有需要可以查看
一文看懂Mysql优化问题【纯肝货、面试必备】
第一层是服务器层,主要提供连接处理、授权认证、安全等功能。
第二层实现了 MySQL 核心服务功能,包括查询解析、分析、优化、缓存以及日期和时间等所有内置函 数,所有跨存储引擎的功能都在这一层实现,例如存储过程、触发器、视图等。
第三层是存储引擎层,存储引擎负责 MySQL 中数据的存储和提取。服务器通过 API 与存储引擎通信, 这些接口屏蔽了不同存储引擎的差异,使得差异对上层查询过程透明。除了会解析外键定义的 InnoDB 外,存储引擎不会解析 SQL,不同存储引擎之间也不会相互通信,只是简单响应上层服务器请求。
在处理并发读或写时,可以通过实现一个由两种类型组成的锁系统来解决问题。这两种类型的锁通常被 称为共享锁和排它锁,也叫读锁和写锁。读锁是共享的,相互不阻塞,多个客户在同一时刻可以同时读 取同一个资源而不相互干扰。写锁则是排他的,也就是说一个写锁会阻塞其他的写锁和读锁,确保在给 定时间内只有一个用户能执行写入并防止其他用户读取正在写入的同一资源。
在实际的数据库系统中,每时每刻都在发生锁定,当某个用户在修改某一部分数据时,MySQL 会通过 锁定防止其他用户读取同一数据。写锁比读锁有更高的优先级,一个写锁请求可能会被插入到读锁队列 的前面,但是读锁不能插入到写锁前面。
表锁是MySQL中最基本的锁策略,并且是开销最小的策略。表锁会锁定整张表,一个用户在对表进行写 操作前需要先获得写锁,这会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他读取的用户 才能获取读锁,读锁之间不相互阻塞。
行锁可以最大程度地支持并发,同时也带来了最大开销。InnoDB 和 XtraDB 以及一些其他存储引擎实 现了行锁。行锁只在存储引擎层实现,而服务器层没有实现。
死锁是指多个事务在同一资源上相互占用并请求锁定对方占用的资源而导致恶性循环的现象。当多个事
务试图以不同顺序锁定资源时就可能会产生死锁,多个事务同时锁定同一个资源时也会产生死锁。
为了解决死锁问题,数据库系统实现了各种死锁检测和死锁超时机制。越复杂的系统,例如InnoDB 存 储引擎,越能检测到死锁的循环依赖,并立即返回一个错误。这种解决方式很有效,否则死锁会导致出 现非常慢的查询。还有一种解决方法,就是当查询的时间达到锁等待超时的设定后放弃锁请求,这种方 式通常来说不太好。InnoDB 目前处理死锁的方法是将持有最少行级排它锁的事务进行回滚。
死锁发生之后,只有部分或者完全回滚其中一个事务,才能打破死锁。对于事务型系统这是无法避免 的,所以应用程序在设计时必须考虑如何处理死锁。大多数情况下只需要重新执行因死锁回滚的事务即 可。
事务是一组原子性的 SQL 查询,或者说一个独立的工作单元。如果数据库引擎能够成功地对数据库应用 该组查询的全部语句,那么就执行该组查询。如果其中有任何一条语句因为崩溃或其他原因无法执行, 那么所有的语句都不会执行。也就是说事务内的语句要么全部执行成功,要么全部执行失败。
MVCC 是多版本并发控制,在很多情况下避免加锁,大都实现了非阻塞的读操作,写操作也只锁定必要
的行。
InnoDB 的MVCC 通过在每行记录后面保存两个隐藏的列来实现,这两个列一个保存了行的创建时间, 一个保存行的过期时间间。不过存储的不是实际的时间值而是系统版本号,每开始一个新的事务系统版 本号都会自动递增,事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本 号进行比较。
MVCC 只能在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作,因为 READ UNCOMMITTED 总是读取最新的数据行,而不是符合当前事务版本的数据行,而 SERIALIZABLE 则会对 所有读取的行都加锁。
InnoDB 是 MySQL 的默认事务型引擎,用来处理大量短期事务。InnoDB 的性能和自动崩溃恢复特性使
得它在非事务型存储需求中也很流行,除非有特别原因否则应该优先考虑 InnoDB。
InnoDB 的数据存储在表空间中,表空间由一系列数据文件组成。MySQL4.1 后 InnoDB 可以将每个表
的数据和索引放在单独的文件中。
InnoDB采用MVCC来支持高并发,并且实现了四个标准的隔离级别。其默认级别是 REPEATABLE READ ,并通过间隙锁策略防止幻读,间隙锁使 InnoDB 不仅仅锁定查询涉及的行,还会对索引中的间 隙进行锁定防止幻行的插入。
InnoDB 表是基于聚簇索引建立的,InnoDB 的索引结构和其他存储引擎有很大不同,聚簇索引对主键 查询有很高的性能,不过它的二级索引中必须包含主键列,所以如果主键很大的话其他所有索引都会很
大,因此如果表上索引较多的话主键应当尽可能小。
InnoDB 的存储格式是平***立的,可以将数据和索引文件从一个平台复制到另一个平台。
InnoDB 内部做了很多优化,包括从磁盘读取数据时采用的可预测性预读,能够自动在内存中创建加速 读操作的自适应哈希索引,以及能够加速插入操作的插入缓冲区等。
MySQL5.1及之前,MyISAM 是默认存储引擎,MyISAM 提供了大量的特性,包括全文索引、压缩、空
间函数等,但不支持事务和行锁,最大的缺陷就是崩溃后无法安全恢复。对于只读的数据或者表比较 小、可以忍受修复操作的情况仍然可以使用 MyISAM。
MyISAM 将表存储在数据文件和索引文件中,分别以 .MYD 和 .MYI 作为扩展名。MyISAM 表可以包 含动态或者静态行,MySQL 会根据表的定义决定行格式。MyISAM 表可以存储的行记录数一般受限于 可用磁盘空间或者操作系统中单个文件的最大尺寸。
MyISAM 对整张表进行加锁,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但是在 表有读取查询的同时,也支持并发往表中插入新的记录。
对于MyISAM 表,MySQL 可以手动或自动执行检查和修复操作,这里的修复和事务恢复以及崩溃恢复 的概念不同。执行表的修复可能导致一些数据丢失,而且修复操作很慢。
对于 MyISAM 表,即使是 BLOB 和 TEXT 等⻓字段,也可以基于其前 500 个字符创建索引。MyISAM 也支持全文索引,这是一种基于分词创建的索引,可以支持复杂的查询。
MyISAM 设计简单,数据以紧密格式存储,所以在某些场景下性能很好。MyISAM 最典型的性能问题还 是表锁问题,如果所有的查询⻓期处于 Locked 状态,那么原因毫无疑问就是表锁。
如果需要快速访问数据且这些数据不会被修改,重启以后丢失也没有关系,那么使用 Memory 表是非 常有用的。Memory 表至少要比 MyISAM 表快一个数量级,因为所有数据都保存在内存,不需要磁盘 IO,Memory 表的结构在重启后会保留,但数据会丢失。
Memory 表适合的场景:查找或者映射表、缓存周期性聚合数据的结果、保存数据分析中产生的中间数 据。
Memory 表支持哈希索引,因此查找速度极快。虽然速度很快但还是无法取代传统的基于磁盘的表, Memory 表使用表级锁,因此并发写入的性能较低。它不支持 BLOB 和 TEXT 类型的列,并且每行的⻓ 度是固定的,所以即使指定了 VARCHAR 列,实际存储时也会转换成CHAR,这可能导致部分内存的浪 费。
如果 MySQL 在执行查询的过程中需要使用临时表来保持中间结果,内部使用的临时表就是 Memory 表。如果中间结果太大超出了Memory 表的限制,或者含有 BLOB 或 TEXT 字段,临时表会转换成 MyISAM 表。
简单来说分为五步:1 客户端发送一条查询给服务器。2 服务器先检查查询缓存,如果命中了缓存则
立刻返回存储在缓存中的结果,否则进入下一阶段。3 服务器端进行 SQL 解析、预处理,再由优化器 生成对应的执行计划。4 MySQL 根据优化器生成的执行计划,调用存储引擎的 API 来执行查询。5 将 结果返回给客户端。
VARCHAR 用于存储可变字符串,是最常⻅的字符串数据类型。它比 CHAR 更节省空间,因为它仅使用 必要的空间。VARCHAR 需要 1 或 2 个额外字节记录字符串⻓度,如果列的最大⻓度不大于 255 字节则 只需要 1 字节。VARCHAR 不会删除末尾空格。
VARCHAR 适用场景:字符串列的最大⻓度比平均⻓度大很多、列的更新很少、使用了 UTF8 这种复杂 字符集,每个字符都使用不同的字节数存储。
CHAR 是定⻓的,根据定义的字符串⻓度分配足够的空间。CHAR 会删除末尾空格。
CHAR 适合存储很短的字符串,或所有值都接近同一个⻓度,例如存储密码的 MD5 值。对于经常变更 的数据,CHAR 也比 VARCHAR更好,因为定⻓的 CHAR 不容易产生碎片。对于非常短的列,CHAR 在 存储空间上也更有效率,例如用 CHAR 来存储只有 Y 和 N 的值只需要一个字节,但是 VARCHAR 需要 两个字节,因为还有一个记录⻓度的额外字节。
DATETIME 能保存大范围的值,从 1001~9999 年,精度为秒。把日期和时间封装到了一个整数中,与 时区无关,使用 8 字节存储空间。
TIMESTAMP 和 UNIX 时间戳相同,只使用 4 字节的存储空间,范围比 DATETIME 小得多,只能表示 1970 ~2038 年,并且依赖于时区。
索引也叫键,是存储引擎用于快速找到记录的一种数据结构。索引对于良好的性能很关键,尤其是当表 中数据量越来越大时,索引对性能的影响愈发重要。在数据量较小且负载较低时,不恰当的索引对性能 的影响可能还不明显,但数据量逐渐增大时,性能会急剧下降。
索引大大减少了服务器需要扫描的数据量、可以帮助服务器避免排序和临时表、可以将随机 IO 变成顺
序 IO。但索引并不总是最好的工具,对于非常小的表,大部分情况下会采用全表扫描。对于中到大型的表, 索引就非常有效。但对于特大型的表,建立和使用索引的代价也随之增⻓,这种情况下应该使用分 区技 术。
在MySQL中,首先在索引中找到对应的值,然后根据匹配的索引记录找到对应的数据行。索引可以包括 一个或多个列的值,如果索引包含多个列,那么列的顺序也十分重要,因为 MySQL 只能使用索引的最 左前缀。
大多数 MySQL 引擎都支持这种索引,但底层的存储引擎可能使用不同的存储结构,例如 NDB 使用 T- Tree,而 InnoDB 使用 B+ Tree。
B-Tree 通常意味着所有的值都是按顺序存储的,并且每个叶子⻚到根的距离相同。B-Tree 索引能够加 快访问数据的速度,因为存储引擎不再需要进行全表扫描来获取需要的数据,取而代之的是从索引的根 节点开始进行搜索。根节点的槽中存放了指向子节点的指针,存储引擎根据这些指针向下层查找。通过 比较节点⻚的值和要查找的值可以找到合适的指针进入下层子节点,这些指针实际上定义了子节点⻚中 值的上限和下限。最终存储引擎要么找到对应的值,要么该记录不存在。叶子节点的指针指向的是被索 引的数据,而不是其他的节点⻚。
B-Tree索引的限制:
如果不是按照索引的最左列开始查找,则无法使用索引。
不能跳过索引中的列,例如索引为 (id,name,sex),不能只使用 id 和 sex 而跳过 name。
如果查询中有某个列的范围查询,则其右边的所有列都无法使用索引。
哈希索引基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对 所有的索引列计算一个哈希码,哈希码是一个较小的值,并且不同键值的行计算出的哈希码也不一样。 哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。
只有 Memory 引擎显式支持哈希索引,这也是 Memory 引擎的默认索引类型。 因为索引自身只需存储对应的哈希值,所以索引的结构十分紧凑,这让哈希索引的速度非常快,但它也
有一些限制:
哈希索引数据不是按照索引值顺序存储的,无法用于排序。 哈希索引不支持部分索引列匹配查找,因为哈希索引始终是使用索引列的全部内容来计算哈希值 的。例如在数据列(a,b)上建立哈希索引,如果查询的列只有a就无法使用该索引。
哈希索引只支持等值比较查询,不支持任何范围查询。
自适应哈希索引是 InnoDB 引擎的一个特殊功能,当它注意到某些索引值被使用的非常频繁时,会在内 存中基于 B-Tree 索引之上再创键一个哈希索引,这样就让 B-Tree 索引也具有哈希索引的一些优点,比 如快速哈希查找。这是一个完全自动的内部行为,用户无法控制或配置,但如果有必要可以关闭该功 能。
MyISAM 表支持空间索引,可以用作地理数据存储。和 B-Tree 索引不同,这类索引无需前缀查询。空 间索引会从所有维度来索引数据,查询时可以有效地使用任意维度来组合查询。必须使用 MySQL 的 GIS 即地理信息系统的相关函数来维护数据,但 MySQL 对 GIS 的支持并不完善,因此大部分人都不会 使用这个特性。
需要基于相似度的查询,而不是精确的数值比较,全文索引就是为这种场景设计的。
MyISAM 的全文索引是一种特殊的 B-Tree 索引,一共有两层。第一层是所有关键字,然后对于每一个 关键字的第二层,包含的是一组相关的"文档指针"。全文索引不会索引文档对象中的所有词语,它会根 据规则过滤掉一些词语,例如停用词列表中的词都不会被索引。
聚簇索引不是一种索引类型,而是一种数据存储方式。InnoDB 的聚簇索引实际上在同一个结构中保存 了 B-Tree 索引和数据行。当表有聚餐索引时,它的行数据实际上存放在索引的叶子⻚中,因为无法同 时把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。
优点:1 可以把相关数据保存在一起。2 数据访问更快,聚簇索引将索引和数据保存在同一个 B-Tree 中,因此获取数据比非聚簇索引要更快。3 使用覆盖索引扫描的查询可以直接使用⻚节点中的主键值。
缺点:1 聚簇索引最大限度提高了 IO 密集型应用的性能,如果数据全部在内存中将会失去优势。2 更 新聚簇索引列的代价很高,因为会强制每个被更新的行移动到新位置。3 基于聚簇索引的表插入新行或主 键被更新导致行移动时,可能导致⻚分裂,表会占用更多磁盘空间。4 当行稀疏或由于⻚分裂导致数据存 储不连续时,全表扫描可能很慢。
覆盖索引指一个索引包含或覆盖了所有需要查询的字段的值,不再需要根据索引回表查询数据。覆盖索
引必须要存储索引列的值,因此 MySQL 只能使用 B-Tree 索引做覆盖索引。
优点:1 索引条目通常远小于数据行大小,可以极大减少数据访问量。2 因为索引按照列值顺序存储, 所以对于 IO 密集型防伪查询回避随机从磁盘读取每一行数据的 IO 少得多。3 由于 InnoDB 使用聚簇 索引,覆盖索引对 InnoDB 很有帮助。InnoDB 的二级索引在叶子节点保存了行的主键值,如果二级主 键能覆盖查询那么可以避免对主键索引的二次查询。
如果索引列出现了隐式类型转换,则 MySQL 不会使用索引。常⻅的情况是在 SQL 的 WHERE 条件中字
段类型为字符串,其值为数值,如果没有加引号那么 MySQL 不会使用索引。
如果 WHERE 条件中含有 OR,除非 OR 前使用了索引列而 OR 之后是非索引列,索引会失效。
MySQL 不能在索引中执行 LIKE 操作,这是底层存储引擎 API 的限制,最左匹配的 LIKE 比较会被转换 为简单的比较操作,但如果是以通配符开头的 LIKE 查询,存储引擎就无法做比较。这种情况下 MySQL 只能提取数据行的值而不是索引值来做比较。
如果查询中的列不是独立的,则 MySQL 不会使用索引。独立的列是指索引列不能是表达式的一部分, 也不能是函数的参数。
对于多个范围条件查询,MySQL 无法使用第一个范围列后面的其他索引列,对于多个等值查询则没有 这种限制。
如果 MySQL 判断全表扫描比使用索引查询更快,则不会使用索引。
索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。
可以通过两种方式来定位执行效率较低的 SQL 语句。一种是通过慢查询日志定位,可以通过慢查询日志 定位那些已经执行完毕的 SQL 语句。另一种是使用 SHOW PROCESSLIST 查询,慢查询日志在查询结束 以后才记录,所以在应用反应执行效率出现问题的时候查询慢查询日志不能定位问题,此时可以使用 SHOW PROCESSLIST 命令查看当前 MySQL 正在进行的线程,包括线程的状态、是否锁表等,可以实时 查看 SQL 的执行情况,同时对一些锁表操作进行优化。找到执行效率低的 SQL 语句后,就可以通过 SHOW PROFILE、EXPLAIN 或 trace 等丰富来继续优化语句。
通过 SHOW PROFILE 可以分析 SQL 语句性能消耗,例如查询到 SQL 会执行多少时间,并显示 CPU、
内存使用量,执行过程中系统锁及表锁的花费时间等信息。例如SHOW PROFILE CPU/MEMORY/BLOCK
IO FOR QUERY N 分别查询 id 为 N 的 SQL 语句的 CPU、内存以及 IO 的消耗情况。
从 MySQL5.6 开始,可以通过 trace 文件进一步获取优化器是是如何选择执行计划的,在使用时需要先 打开设置,然后执行一次 SQL,最后查看 information_schema.optimizer_trace 表而都内容,该表为 联合i表,只能在当前会话进行查询,每次查询后返回的都是最近一次执行的 SQL 语句。
执行计划是 SQL 调优的一个重要依据,可以通过 EXPLAIN 命令查看 SQL 语句的执行计划,如果作用在 表上,那么该命令相当于 DESC。EXPLAIN 的指标及含义如下:
指标名 | 含义 |
---|---|
id | 表示 SELECT 子句或操作表的顺序,执行顺序从大到小执行,当 id 一样时,执 行顺序从上往下。 |
select_type | 表示查询中每个 SELECT 子句的类型,例如 SIMPLE 表示不包含子查询、表连接 或其他复杂语法的简单查询,PRIMARY 表示复杂查询的最外层查询, SUBQUERY 表示在 SELECT 或 WHERE 列表中包含了子查询。 |
type | 表示访问类型,性能由差到好为:ALL 全表扫描、index 索引全扫描、range 索 引范围扫描、ref 返回匹配某个单独值得所有行,常⻅于使用非唯一索引或唯一 索引的非唯一前缀进行的查找,也经常出现在 join 操作中、eq_ref 唯一性索引 扫描,对于每个索引键只有一条记录与之匹配、const 当 MySQL 对查询某部分 进行优化,并转为一个常量时,使用这些访问类型,例如将主键或唯一索引置 于 WHERE 列表就能将该查询转为一个 const、system 表中只有一行数据或空 表,只能用于 MyISAM 和 Memory 表、NULL 执行时不用访问表或索引就能得 到结果。SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如 果可以是consts 最好。 |
possible_keys | 表示查询时可能用到的索引,但不一定使用。列出大量可能索引时意味着备选 索引数量太多了。 |
key | 显示 MySQL 在查询时实际使用的索引,如果没有使用则显示为 NULL。 |
key_len | 表示使用到索引字段的⻓度,可通过该列计算查询中使用的索引的⻓度,对于 确认索引有效性以及多列索引中用到的列数目很重要。 |
ref | 表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值。 |
rows | 表示 MySQL 根据表统计信息及索引选用情况,估算找到所需记录所需要读取的 行数。 |
Extra | 表示额外信息,例如 Using temporary 表示需要使用临时表存储结果集,常⻅ 于排序和分组查询。Using filesort 表示无法利用索引完成的文件排序,这是 ORDER BY 的结果,可以通过合适的索引改进性能。Using index 表示只需要使用索引就可以满足查询表得要求,说明表正在使用覆盖索引。 |
复制解决的基本问题是让一台服务器的数据与其他服务器保持同步,一台主库的数据可以同步到多台备 库上,备库本身也可以被配置成另外一台服务器的主库。主库和备库之间可以有多种不同的组合方式。
MySQL 支持两种复制方式:基于行的复制和基于语句的复制,基于语句的复制也称为逻辑复制,从 MySQL 3.23 版本就已存在,基于行的复制方式在 5.1 版本才被加进来。这两种方式都是通过在主库上 记录二进制日志、在备库重放日志的方式来实现异步的数据复制。因此同一时刻备库的数据可能与主库 存在不一致,并且无法包装主备之间的延迟。
MySQL 复制大部分是向后兼容的,新版本的服务器可以作为老版本服务器的备库,但是老版本不能作 为新版本服务器的备库,因为它可能无法解析新版本所用的新特性或语法,另外所使用的二进制文件格 式也可能不同。
复制解决的问题:数据分布、负载均衡、备份、高可用性和故障切换、MySQL 升级测试。
1 在主库上把数据更改记录到二进制日志中。 2 备库将主库的日志复制到自己的中继日志中。 3 备库读 取中继日志中的事件,将其重放到备库数据之上。
第一步是在主库上记录二进制日志,每次准备提交事务完成数据更新前,主库将数据更新的事件记录到 二进制日志中。MySQL 会按事务提交的顺序而非每条语句的执行顺序来记录二进制日志,在记录二进 制日志后,主库会告诉存储引擎可以提交事务了。
下一步,备库将主库的二进制日志复制到其本地的中继日志中。备库首先会启动一个工作的 IO 线程, IO 线程跟主库建立一个普通的客户端连接,然后在主库上启动一个特殊的二进制转储线程,这个线程会读 取主库上二进制日志中的事件。它不会对事件进行轮询。如果该线程追赶上了主库将进入睡眠状态, 直 到主库发送信号量通知其有新的事件产生时才会被唤醒,备库 IO 线程会将接收到的事件记录到中继
日志中。
备库的 SQL 线程执行最后一步,该线程从中继日志中读取事件并在备库执行,从而实现备库数据的更 新。当 SQL 线程追赶上 IO 线程时,中继日志通常已经在系统缓存中,所以中继日志的开销很低。SQL 线程执行的时间也可以通过配置选项来决定是否写入其自己的二进制日志中。
希望博主收集的内容能帮到大家,祝大家能找到一个好的工作,过好的生活,如有错误欢迎指正。