书籍 -- 《高性能MySQL》持续更新中(六)

第七章 MySQL高性能特性

1 分区表

  • MySQL实现分区表的方式——对底层表的封装——意味着索引也是按照分区的子表定义的,而没有全局索引。

  • 分区表的应用场景以及优势:

    • 表非常大以至于无法全部都放在内存中,或者只在表的最后部分有热点数据,其他均是历史数据。
    • 分区表的数据更容易维护。
    • 分区表的数据可以分布在不同的物理设备上,从而高效地利用多个硬件设备。
  • 分区表本身也有一些限制,下面是其中比较重要的几点:

    • 一个表最多只能有1024个分区。
    • 在MySQL5.1中,分区表达式必须是整数,或者是返回整数的表达式。在MySQL5.5中,某些场景可以直接使用列进行分区。
    • 如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来。
    • 分区表中无法使用外键约束。

1.1 分区表的原理

  • 分区表是由多个相关的底层表实现,这些底层表也是由句柄对象表示,所以我们也可以直接访问各个分区。存储引擎管理分区的各个底层表和管理普通表一样(所有的底层表都必须使用相同的存储引擎),底层表和一个普通表没有任何不同,存储引擎也无须知道这是一个普通表还是底层表。
  • UPDATE操作:当更新一条记录时,分区层先打开并锁柱所有的底层表,MySQL先确定需要更新的记录在哪个分区,然后取出数据并更新,在判断更新后的数据应该放在哪个分区,最后对底层表进行写入操作,并对原数据所在底层表进行删除操作。

1.2 分区表的类型

  • 分区技术:

    • 根据键值进行分区,来减少innodb的互斥竞争。
    • 使用数学模型函数进行分区,然后将数据轮询放入不同的分区。例如,可以对日期做模7的运算,或者更简单地使用返回周几的函数,如果只想保留最近几天的数据,这样分区很方便。
    • 假设表有一个自增的主键id,希望根据时间将最近的热点数据集中存放。那么必须将时间戳包含在主键当中才行,而这和主键本生的意义相矛盾。这种情况下也可以使用这样的分区表达式来实现相同的目的:hash(id,div,1000000),这将为100万数据建立一个分区。这样一方面实现了当初的分区目的,另一方面比起使用时间范围分区还避免了一个问题,就是当超过一定阈值时,如果使用时间范围分区就必须新增分区。

1.3 如何使用分区表

  • 全量扫描数据,不要任何索引

可以使用简单的分区方式存放表,不要任何索引,根据分区的规则大致定位需要的数据位置。

  • 索引数据,分离热点

如果数据有明显的热点,而且除了这部分数据,其他数据很少被访问到,那么可以将这部分热点数据单独存放字一个分区中,让这个分区的数据能够有机会都缓存在内存中。

1.4 什么情况下会出问题

  • null值会使分区过滤无效:MySQL5.5已经解决这个问题了。

  • 分区列和索引不匹配,会导致查询无法进行分区过滤。假设在列A上定义了索引,而在列B上进行分区,因为每个分区否有其独立的索引,所以扫描列B上的索引就需要扫描每一个分区内对应的索引。

  • 选择分区的成本可能很高。

  • 打开并锁柱所有底层表的成本可能很高。

  • 维护分区的成本可能很高。重组分区的原理与alter类似,先创建一个临时的分区,然后将数据复制到其中,最后再删除原分区。

  • 分区表的一些限制

    • 所有分区都必须使用相同的存储引擎。
    • 分区函数中可以使用的函数和表达式也有一些限制。
    • 某些存储引擎不支持分区。
    • 对于myisam的分区表,不能再使用LOAD INDEX INTO CACHE等。
    • 对于myisam表,使用分区表时需要打开更多的文件描述符。

1.5 查询优化

  • 引入分区给查询优化带来了一些新的思路。分区最大的优点就是优化器可以根据分区函数来过滤一些分区。根据粗粒度索引的优势,通过分区过滤通常可以让查询扫描更少的数据。所以,对于分区表来说,很重要的一点要在where条件中带入分区列,有时候即使看似多余的也要带上,这样就可以让优化器能够过滤掉无须访问的分区,如果没有这些条件,mysql就需要让对应存储引擎访问这个表的所有分区,如果表非常大的话,就可能会非常慢。
  • MySQL只能使用分区函数的列本身进行比较才能过滤分区,而不能根据表达式的值去过滤分区,即使这个表达式就是分区函数也不行。所以优化器能够利用这个条件过滤部分分区,一个很重要的原则就是:即便在创建分区时可以使用表达式,但在查询时却只能根据列来过滤分区。

1.6 合并表

  • 删除一个合并表,它的子表不会受任何影响,而如果直接删除一个子表则可能会有不同的后果,这要视操作系统而定。

1.7 创建视图

  • MySQL5.0版本之后开始引入视图。视图本身是一个虚拟表,不存放任何数据。在使用SQL语句访问视图的时候,他返回的数据是MySQL从其他表中生成的。视图和表是在同一个命名空间,MySQL在很多地方对于视图和表时同样对待的。
  • MySQL可以使用两种方法中的任何一种来处理视图,这两种算法分别称为合并算法(MERGE)和临时表算法(TEMPTABLE)如下图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KZX81mFJ-1615253419853)(http://images1.poetchao.com//mysql/01/01.png)]

  • 如果视图中包含GROUP BY、DISTINCT、任何聚合函数、UNION、子查询等。只要无法在原表记录和视图记录中建立一一映射的场景中,MySQL都将使用临时表算法来实现视图表。

1.8 可更新视图

  • 如果视图定义中包含了GROUP BY、UNION、聚合函数,以及其他一些特殊情况,就不能被更新了。被更新的列必须来自同一个表中,另外,所有使用临时表算法实现的视图都无法被更新。

3 外键约束

  • 外键通常都要求每次在修改数据时都要在另外一张表中多执行一次查找操作。
  • 外键在相关数据的删除和更新上,也比在应用中维护要更高效,不过,外键维护操作是逐行进行的,所以这样的更新会比批量删除和更新要慢些。
  • 外键约束使得查询需要访问一些别的表,这也意味着需要额外的锁。如果向子表中写入一条记录,外键约束会让innodb检查对应的附表的记录,也就需要对父表对应记录进行加锁操作,来确保这条记录不会在这个事务完成之时就被删除了,这会导致额外的所等待,甚至会导致死锁。

4 触发器

  • 对于每一个表的每一个事件,最多只能定义一个触发器(换句话说,不能再after insert上定义两个触发器)。
  • MySQL只支持基于行的触发器,也就是说,触发器始终是针对一条记录的。而不是针对整个SQL语句的。如果变更的数据集非常大的话,效率会很低。
  • 触发器的可以掩盖服务器背后的工作,一个简单的SQL语句背后,因为触发器,可能包含了很多看不见的工作。例如,触发器可能会更新另一个相关表,那么这个触发器会让这条SQL影响的记录数翻一倍。
  • 触发器可能导致死锁和锁等待。如果触发器失败,那么原来的SQL语句也会失败。如果没有意识到这其中触发器在搞鬼,那么很难理解服务器抛出的错误代码是什么意思。
  • 触发器并不能一定保证更新的原子性。例如,一个触发器在更新myisam表的时候,如果遇到什么错误,是没有办法做回滚操作的。假设你在一个myisam表上建立一个after-update的触发器,用来更新另一个myisam表。如果触发器在更新第二个表的时候遇到错误导致更新失败,那么第一个表的更新并不会回滚。
  • 可以使用触发器来记录数据变更日志。
  • 在innodb表上的触发器是在同一个事务中完成的,所以他们执行的操作是原子的,原操作和触发器操作会同时失败或者成功。不过,如果在innodb表上建触发器去检查数据的一致性,需要特别小心MVCC,稍不小心,你可能会获得错误的结果。

5 游标

  • 因为MySQL游标中指向的对象都是存储在临时表中而不是实际查询到的数据,所以MySQL游标总是只读的。

6 字符集和校对

  • 字符集是指一种从二进制编码到某类字符符号的映射,可以参考如何使用一个字节来表示英文字母,校对是指一组用于某个字符集的排序规则。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RN4Bl4s2-1615253419855)(http://images1.poetchao.com//mysql/character/02.png)]

  • 如果比较的两个字符串的字符集不同,MySQL会先将其转成同一个字符集再进行比较。如果两个字符集不兼容的话,则会抛出错误。

7 全文索引

  • myisam的全文索引作用是对一个“全文集合”,这可能是某个数据表的一列,也可能是多个列。具体的,对数据表的某一条记录,myisam会将需要索引的列全部拼接成一个字符串,然后进行索引。
  • myisam的全文索引是一类特殊的B-Tree索引,共有两层。第一层是所有关键字,然后对于每一个关键字的第二层,包含的是一组相关的“文档指针”。
  • 在MATCH()函数中指定的列必须和在全文索引中指定法人列完全相同,否则人就无法使用全文索引。这是因为全文索引不会记录关键字是来自哪一列。

8 查询缓存

  • 查询缓存系统会跟踪查询中涉及的每个表,如果这些表发生变化,那么和这个表相关的所有的缓存都将失效。这种机制效率看起来比较低,因为数据表变化时很有可能对应的查询结果并没有变更,但是这种简单实现很小,而这点对于一个非常繁忙的系统来说非常重要。

  • 随着现在的通用服务器越来越强大,查询缓存被发现是一个影响服务器扩展性的因素。它可能成为整个服务器的资源竞争单点,在多核服务器上还可能导致服务器僵死。后面我们将详细介绍如何配合查询缓存,但是很多时候我们还是认为应该默认关闭查询缓存,如果查询缓存作用很大的话,那就配置一个很小的查询缓存空间。

  • MySQL判断命中的方法很简单:缓存存放在一个引用表中,通过一个哈希值引用,这个哈希值包括了如下因素,即查询本身、当前要查询的数据库、客户端协议的版本等。

  • 如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、mysql库中的系统表,或者任何包含列级别权限的表,都不会被缓存。

  • 我们常听到:如果查询中包含一个不确定的函数,MySQL则不会检查查询缓存。这个说法是不正确的。因为在检查查询缓存的时候,还没有解析SQL语句,所以MySQL并不知道查询语句中是否包含这类函数。在检查查询缓存之前,MySQL只做一件事,就是通过一个大小写不敏感的检查看看SQL语句是不是一SEL开头。

  • 准确的说法应该是:如果查询语句中包含任何的不确定函数,那么在查询缓存中是不可能找到缓存结果的。所以,如果希望换成一个带日期的查询,那么最好将日期提前计算好。

  • 因为查询缓存是在完整的select语句基础上的,而且只是在刚收到SQL语句的时候才检查,所以子查询和存储过程都没办法使用查询缓存。

  • 打开查询缓存对读和写操作都会带来额外的消耗:

    • 读查询在开始之前必须先检查是否命中缓存。
    • 如果这个读查询可以被缓存,那么当完成执行后,MySQL若发现查询缓存中没有这个查询,会将其结果存入查询缓存,这会带来额外的系统开销。
    • 这对写操作也会有影响,因为当向某个表写入数据的时候,MySQL必须将对应表的所有缓存都设置失效。如果查询缓存非常大或者碎片很多,这个操作就可能会带来很大系统消耗。
  • 对于innodb用户来说,事务的一些特性会限制查询缓存的使用。当一个语句在事务中修改了某个表,MySQL会将这个表的对应的查询缓存都设置失效,而事实上,innodb的多版本特性会暂时将这个修改对其他事务屏蔽。在这个事务提交之前,这个表的相关查询是无法缓存的,所以所有在这个表上面的查询——内部或外部的事务——都只能在该事务提交之后被缓存。因此,长时间运行的事务,会大大降低查询缓存的命中率。

  • 在收缩第一个查询结果使用的缓存空间时,就会在第二个查询结果之间留下一个‘空隙’——一个非常小的空闲空间,因为小于query_cache_min_res_unit而不能再次被查询缓存使用。这类空隙我们称为“碎片”。

  • 缓存位命中可能有如下几种原因:

    • 查询语句无法被缓存,可能是因为查询中包含一个不确定的函数,或者查询结果太大而无法缓存。
    • MySQL从未处理这个查询,所以结果也从不曾被缓存过。
    • 还有一种情况是虽然之前缓存了查询结果,但是由于查询缓存的内存用完了,MySQL需要将某些缓存‘逐出’,或者由于数据表被修改导致缓存失效。
  • 如果服务器上有大量缓存命中,但是实际上绝大多数查询都被缓存了,那么一定是有如下情况发生:

    • 查询缓存还没有完成预热。也就是说,没有MySQL还没有机会将查询结果缓存起来。
    • 查询语句之前从未执行过。如果你的应用程序不会重复执行一条查询语句,那么即使完成预热仍然会有很多缓存未命中。
    • 缓存失效操作太多了。

9 如何配置和维护查询缓存

  • query_cache_type是否打开查询缓存。
  • query_cache_size查询缓存使用的总内存空间,单位是字节。
  • query_cache_min_res_unit在查询缓存中分配内存块时的最小单位。
  • query_cache_limitMySQL能够缓存的最大查询结果。如果查询结果大于这个值,则不会被缓存。
  • query_cache_wlock_invalidate如果某个数据表被其他的连接锁住,是否仍然从查询缓存中返回结果。

在这里插入图片描述

  • 字符集是一种字节到字符之间的映射,而校对规则是指一个字符集的排序方法。

Guess you like

Origin blog.csdn.net/qq_38721452/article/details/114574972
Recommended