ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

高性能MySQL

2021-06-16 20:33:03  阅读:138  来源: 互联网

标签:事务 存储 查询 索引 高性能 哈希 MySQL 数据


MySQL架构与历史

  1. 并发控制
    • 共享锁(读锁):共享、互不阻塞的。多个客户同一时刻可以同时读取一个资源而互不干扰。
    • 排他锁(写锁):排他的、一个写锁会阻塞其他的写锁和读锁,防止其他用户读取正在写入的同一资源。
    • 表锁:锁定整张表,一个用户在对表进行写操作前,需要先获得写锁,这会阻塞其他用户对表的读写操作,没有写锁时才能获取到读锁,读锁之间相互不阻塞。
    • 行级锁:最大程度支持并发处理。对数据行进行锁定,降低了锁粒度,让多个用户能同时对表进行操作。
    • 意向锁
  2. 事务
    事务是一组原子性的SQL查询,事务内的语句要么全部执行成功,要么全部执行失败。
    1. ACID特性
      • 原子性:一个事务必须被视为一个不可分割的最小工作单元,整个事务中所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作。
      • 一致性:几个并发执行的事务,其执行结果必须与按某一顺序串行执行的结果一直。数据库总是从一个一致性的状态转换到另外一个一致性的状态。
      • 隔离性:事务执行不受到其他事务的干扰,并行事务之间相互独立,一个事务所做的修改在最终提交以前,对其他事务是不可见的
      • 持久性:一旦事务提交,则其所做的修改就会永久保存到数据库中,就算崩溃,修改的数据也不会丢失。
    2. 四个隔离级别
      • 脏读:一个事务执行过程中读取到了另一个事务未提交的数据。
      • 丢失修改:一个事务读取一个数据时,另一个事务也访问这个数据,两个事务对这个数据进行了修改,导致第一个修改的结果丢失。
      • 不可重复读: 一个事务范围内查询多次,但返回数据值不同,这是因为查询间隔数据被另一个事务修改并提交了。 这里强调的是数据被修改。
      • 幻读:一个事务在两次查询同一个内容后返回的结果数量不一致(多或少了一些记录),这是因为另一个并发事务在查询间隔增加了记录导致的。这里强调的是数据记录的数量变化。
      • Read Uncommitted(读未提交):事务可以读取未提交的数据,会导致脏读、幻读、不可重复读,是最低的隔离级别。
      • Read Committed(读已提交):只有在事务提交后,更新结果才会被其他事务看见。可以防止脏读,但不能防止幻读和可重复度。
      • Repeatable Read(可重复读):保证在同一个事务中,对同一份数据的读取结果总是相同的,无论是否有其他事务对这份数据进行操作,以及这个事务是否提交。可避免脏读、不可重复读的发生。
      • Serializable(串行化):强制事务串行执行,牺牲了系统并发性。该级别可以防⽌脏读、不可重复读以及幻读。
    3. 死锁:指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。只有部分或者完全回滚其中一个事务才能打破死锁。InnoDB处理的方法:将持有最少行级排他锁的事务回滚
    4. 事务日志:事务日志可以提高事务的效率。存储引擎在修改表的数据时只需要修改其内存拷贝,再把该修改行为记录到持久在硬盘上的事务日志中,而不用每次都将修改的数据本身持久到磁盘。事务日志持久以后,内存中被修改的数据在后台可以慢慢地刷回到磁盘。如果系统崩溃且数据还未写回磁盘,存储引擎在重启时还能自动恢复这部分修改的数据。
  3. MVCC多版本并发控制
    1. MVCC为了解决读写冲突。能在很多情况下避免加锁,开销更低,读操作非阻塞,写操作只锁定必要的行。
    2. 实现:通过保存数据再某个时间点的快照来实现。不管执行多长时间,每个事务看到的数据都是一致的。根据事务开始时间不同,每个事务对同一张表看到的数据可能不一样。
    3. InnoDB的MVCC:通过每一行记录后面保存两个隐藏的列来实现,一个列保存了行的创建时间(系统版本号),一个列保存的是行的过期时间(系统版本号),每次开始一个新事务,系统版本号都会自动递增,事务开始时刻的系统版本号会作为事务的版本号, 用来与查询到的每行记录的版本号比较。
      详细内容:https://blog.csdn.net/SnailMann/article/details/94724197
  4. 存储引擎
    1. InnoDB基于聚簇索引,支持行级锁、外键、MVCC、 事务和崩溃后的安全恢复。InnoDB采用MVCC来支持高并发,并且实现了四个标准的隔离级别。其默认级别是REPEATABLE READ (可重复读),并且通过间隙锁策略防止幻读的出现。间隙锁使得InnoDB不仅仅锁定查询涉及的行,还会对索引中的间隙进行锁定,以防止幻影行的插入。
      在InnoDB中,.ibd的文件,存储与该表相关的数据、索引、表的内部数据字典(表缓存)信息;.frm表结构文件。
    2. MyISAMMyISAM支持表锁、全文索引、压缩、空间函数等,但不支持事务和行级锁,崩溃后无法安全恢复。MyISAM将表存储在两个文件中:数据文件(.MYD)、索引文件(.MYI)。frm文件存储表定义。
    3. Blackhole引擎,它会丢弃所有插入的数据,不做任何保存。但会记录日志。
    4. CSV引擎,可以将CSV文件作为MySQL表来处理,可以作为一种数据交换机制。
    5. Memory引擎,数据存在内存中,访问速度快,重启后数据会丢失。
  5. 如何转换表的引擎:ALTER TABLE tablename ENGINE= InnoDB、将数据导出文件,修改文件中CREATE TABLE语句的存储引擎、先创建一个新的存储引擎的表,然后利用INSERT…SELECT语法来导数据

问题】:如果需要对记录的日志做分析报表。生成报表的SQL很有可能会导致插入效率明显降低,这时候该怎么办?
解决方法】:1. 利用MySQL内置的复制方案将数据复制一份到备库,然后在备库上
执行比较消耗时间和CPU的查询。这样主库只用于高效的插入工作,而备库上执行的查
询也无须担心影响到日志的插入性能。
2. 另外一种方法,在日志记录表的名字中包含年和月的信息,在没有插入操作的历史表上做频繁的查询操作,不会干扰到最新的当前表上的插入操作。

Schema与数据类型优化

  1. 选择优化的数据类型

    • 尽量选择能正确存储数据的最小数据类型。更小的类型通常更快,占用的磁盘、内存、CPU缓存、需要的CPU周期更少
    • 选择简单数据类型,操作通常需要更少的CPU周期
    • 尽量避免NULL。可为NULL的列使得索引、索引统计和值比较都更复杂,会使用更多的存储空间。当可为NULL的列被索引时,每个索引记录需要一个额外的字节,在MyISAM里甚至还可能导致固定大小的索引。
  2. VARCHAR和CHAR的区别

    • VARCHAR类型用于存储可变长字符串,是最常见的字符串数据类型。它比定长类型
      更节省空间,因为它仅使用必要的空间。
    • VARCHAR需要使用1或2个额外字节记录字符串的长度:如果列的最大长度小于或
      等于255字节,则只使用1个字节表示,否则使用2个字节。
    • CHAR类型是定长的,当存储CHAR值时,MySQL会删除所有的末尾空格,适合存储很短或者所有值都接近同一个长度的字符串,不容易产生碎片。
  3. 范式和反范式

    • 第一范式:所有的属性都不可再分,都是原子的。
    • 第二范式:在第一范式的基础上,消除非主属性对码的部分依赖
    • 第三范式:在第二范式的基础上,消除了非主属性对码的传递依赖
    • BCNF::在 3NF 的基础上消除主属性对于码的部分与传递函数依赖
    • 反范式:通过增加冗余数据或数据分组来提高数据库读性能的过程。

    范式的优点

     1. 范式化的更新操作通常比反范式化要快。
     2. 当数据较好地范式化时,就只有很少或者没有重复数据,所以只需要修改更少的数据。
     3. 范式化的表通常更小,可以更好地放在内存里,所以执行操作会更快。
     4. 很少有多余的数据意味着检索列表数据时更少需要DISTINCT或者GROUPBY语句。
    

    反范式的优点:所有数据都在一张表中,避免关联,避免了随机I/O,能使用更有效的索引策略

  4. 加速ALTER TABLE表的操作

    1. 现在一台不提供服务的机器上执行ALTER TABLE操作,然后和提供服务的主库进行切换。
    2. 创建一张和源表无关的新表,然后通过重命名和删表操作交换两张表及里面的数据。

索引

索引可以包含一个或者多个列的值,MySQL只能高效地使用索引的最左前缀列,是用来快速找到记录的一种数据结构。

  1. 索引类型

    1. B-Tree索引: B-Tree通常意味着所有的值都是按顺序存储的,并且每一个叶子页到根的距离相同。B-Tree索引能够加快访问数据的速度因为存储引擎不再需要进行全表扫描来获取需要的数据,取而代之的是从索引的根节点开始进行搜索。根节点的槽中存放了指向子节点的指针,存储引擎根据这些指针向下层查找。通过比较节点页的值和要查找的值可以找到合适的指针进入下层子节点。最终存储引擎要么是找到对应的值,要么该记录不存在。

    B-Tree索引的限制
    1. 如果不是按照索引的最左列开始查找则无法使用索引。
    2. 不能跳过索引中的列。
    3. 如果查询中有某个列的范围查询,则其右边所有列都无法使用索引优化查找。

    1. 哈希索引(Memory存储引擎)
      基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码,哈希码是一个较小的值,并且不同键值的行计算出来的哈希码也不一样。哈希索引将所有的哈希码存储在索引中同时在哈希表中保存指向每个数据行的指针。如果发生了哈希冲突,就通过链表的形式存储。
      哈希索引的限制:
      * 哈希索引只包含哈希值和行指针,并没有存储字段,不能用来避免行读取。
      * 哈希索引数据并不是按照索引值顺序存储的,所以无法用于排序
      * 不支持部分索引列匹配查找,因为哈希索引始终是使用索引值的全部内容来计算哈希值。
      * 只支持等值比较查询(=,IN()等),不支持范围查询,因为每次查询都是用精确的哈希值查找。
      * 发生哈希冲突时,存储引擎必须遍历链表的所有行指针,进行比较查找
      * 如果冲突很多,维护成本很高。
  2. 索引的优点

    1. 大大减少了服务器需要扫描的数据量。
    2. 可以帮助服务器避免排序和临时表。
    3. 可以将随机I/O变为顺序I/O。
  3. 高性能索引策略

    1. 独立的列。:索引列不能是表达式的一部分和函数的参数。
      在这里插入图片描述

    2. 前缀索引:如果索引列的值过长,可以选择开始的部分前缀来建立索引以提高索引效率,但是无法使用前缀索引做Order by和Group by 操作,也无法做覆盖扫描。
      在这里插入图片描述

    3. 聚簇索引
      在聚簇索引中,确定表中数据的顺序,叶子节点的存放的就是表中的数据,因为无法把数据行存放在两个地方,所以一个表只能有一个聚簇索引。
      在InnoDB中建立的二级索引,叶子节点中存储主键值,每次查找数据时,根据索引找到叶子节点中的主键值,根据主键值再到聚簇索引中得到完整的一行记录。
      【为什么要有二级索引?】:相比于聚簇索引,二级索引行放的只是主键值不是数据,占用的空间比较少,而且InnoDB在移动行时,无需维护二级索引,因为叶子节点中存储的是主键值,而不是指针。

    4. 非聚簇索引
      该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同。且非聚簇索引的叶子节点中存放的是指向数据行的指针
      在这里插入图片描述

    5. 聚簇索引的优点

      1. 可以把相关数据保存在一起。
      2. 提高访问速度,数据访问更快。不需要和非聚簇索引一样找到数据的存放地址再去读取。
      3. 使用覆盖索引扫描的查询可以直接使用页节点中的主键值。
    6. 聚簇索引的缺点

      1. 聚簇数据最大限度地提高了I/O 密集型应用的性能,但如果数据全部都放在内存中,则访问的顺序就没那么重要了,聚簇索引也就没什么优势了。
      2. 插入速度严重依赖于插入顺序。按照主键的顺序插入是加载数据到InnoDB表中速度最快的方式。
      3. 更新聚簇索引列的代价很高,因为会强制InnoDB将每个被更新的行移动到新的位置。
      4. 基于聚簇索引的表在插人新行,或者主键被更新导致需要移动行的时候,可能面临“页分裂(page split)” 的问题。当行的主键值要求必须将这一 行插入到某个已满的页中时,存储引擎会将该页分裂成两个页面来容纳该行,这就是一次页分裂操作。页分裂会导致表占用更多的磁盘空间。
      5. 聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连续的时候。
      6. 二级索引(非聚簇索引)可能比想象的要更大,因为在二级索引的叶子节点包含了引用行的主键列。
      7. 二级索引访问需要两次索引查找,而不是一次。
    7. 覆盖索引 :一个索引包含所有需要查询的字段的值,不用回表查询,覆盖索引必须要存储索引列的值。

    8. 索引和锁:InnoDB只有在访问行的时候才会对其加锁,而索引能减少InnoDB访问的行数,从而减少锁的数量。InnoDB在二级索引上使用共享锁,但访问主键索引需要使用排序锁

查询性能优化

  1. 优化数据访问
    查询性能低下的基本原因:访问的数据太多。通过减少访问的数据量的方式进行优化。

    1. 分析低效查询的两个步骤:
      1. 确认应用程序是否在检索大量超过需要的数据,这会导致访问太多行或太多列。
      2. 确定MySQL服务器层是否在分析大量超过需要的数据行。
    2. 数据请求了不需要的数据的案例。(第一个步骤)
      * 查询了不需要的数据,例如可能查询了全部的结果集后客户端再进行筛选,可以在查询后面加上limit来限制
      * 多表关联时返回了全部列
      * 每次都取出了全部列
      * 重复查询了相同的数据,可以将查询的数据缓存起来,需要的时候从缓存中取出。
    3. 判断是否扫描了额外的超过需要的数据行(第二个步骤)
      • 衡量查询开销的三个指标:响应时间、扫描的行数、返回的行数
      • 响应时间:服务时间和排队时间。服务时间是指数据库处理这个查询真正花了多长时间。排队时间是指服务器因为等待某些资源而没有真正执行查询的时间一可能是等I/O操作完成,也可能是等待行锁。
  2. 重构查询的方式

    1. 切分查询。将复杂的查询转化为多个简单查询,将大查询切分成小查询,每个查询功能完全一样,只完成一小部分每次只返回一小部分查询结果,可以避免锁住很多数据、占满事务日志、耗尽系统资源、阻塞很多小的但重要的查询
    2. 分解关联查询:可以对每个表进行一次单表查询,然后将结果在应用程序中进行关联。
      在这里插入图片描述
    3. 分解关联查询的优势
      • 让缓存的效率更高。将关联分解后,如果某个表改变很少,就可以基于该表的查询重复利用查询结果。
      • 单个查询可以减少锁的竞争
      • 更容易对数据库进行拆分,提高高性能和可扩展性
      • 查询效率也会有所提升
      • 可以减少冗余记录的查询
  3. 查询执行的基础

    1. 想MySQL发送请求时,MySQL做了什么?
      1. 客户端发送1条查询给服务器。
      2. 服务器先检查查询缓存,如果命中了缓存,则立刻返回存储在缓存中的结果。否则 进入下一阶段。
      3. 服务器端进行SQL解析、预处理,再由优化器生成对应的执行计划。
      4. MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询。
      5. 将结果返回给客户端。 如果查询可以被缓存,那么MySQL在这个阶段也会将结果存放到查询缓存中。
    2. MySQL客户端和服务器之间的通信协议是“半双工”的,这意味着,在任何一个时刻,要么是由服务器向客户端发送数据,要么是由客户端向服务器发送数据,这两个动作不能同时发生。所以,我们无法也无须将一个消息切成小块独立来发送
    3. MySQL能处理的优化类型
      1. 重新定义关联表的顺序
      2. 将外链接转化成内链接
      3. 使用等价变换规则
      4. 优化COUNT()、MIN()和MAX()
      5. 预估并转化为常数表达式,当检测到一个表达式可以转化为常数的时候,就会一直把该表达式作为常数进行优化处理
      6. 覆盖索引扫描,当索引中的列包含所有查询中需要使用的列的时候,就可以使用索引返回需要的数据,而无须查询对应的数据行
      7. 子查询优化、提前终止查询、等值传播
      8. 列表IN()的比较,MySQL将IN()列表中的数据先进行排序,然后通过二分查找的方式来确定列表中的值是否满足条件
    4. MySQL的排序优化
      当不能使用索引生成排序结果的时候,MySQL要自己进行排序,如果数据量小就在内存中进行,如果数据量大就使用磁盘排序,这一过程成为文件排序
      1. 两次传输排序:读取行指针和需要排序的字段,对其进行排序,然后再根据排序结果读取所需要的数据行。这个过程需要从数据表读取两次数据,第二次读取排序列进行排序后的所有记录,会产生大量的随机I/O,传输成本非常高。
      2. 单词传输排序:先读取查询所需要的所有列,根据给定列进行排序,最后直接返回排序结果,只需要一次顺序I/O,不需要任何的随机I/O
  4. 优化特定类型的查询

    1. 优化count()查询:1. 可以统计某个列值的数量,在统计列值时要求列值是非空的(不统计NULL)。2.可以统计行数。
      * 使用近似值、 使用索引覆盖、使用汇总表、使用外部缓存系统
    2. 优化关联查询:确保ON或者USING子句中的列上有索引、确保任何的GROUP BY和ORDER BY中的表达式只涉及到一个表中的列
    3. 优化子查询:尽量使用关联查询替代子查询
    4. 优化GROUP BY和DISTINCT:使用索引优化、当无法使用索引时,GROUP BY使用两种策略来完成:使用临时表或者文件排序来做分组
    5. 优化limit分页
      1. 尽可能地使用索引覆盖扫描,而不是查询所有的列,然后根据需要做一次关联操作再返回所需的列。
      2. offset会导致MySQL扫描大量不需要的行然后再抛弃掉,如果可以记录上次取数据的位置,下次就可以直接从该记录的位置开始扫描,可以避免使用offset。

标签:事务,存储,查询,索引,高性能,哈希,MySQL,数据
来源: https://blog.csdn.net/qq_45126604/article/details/117953411

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有