分区表是一个独立的逻辑表,但是底层由多个物理子表组成。实现分区的代码实际上是对一组底层表的句柄对象的封装。对分区表的请求,都会通过句柄对象转化成对存储引擎的接口调用。
Mysql实现分区表的方式是对底层表的封装,意味着索引也是按照分区的子表定义的,而没有全局索引。
Mysql在创建表时使用 PARTITION BY 子句定义每个分区存放的数据,在执行查询的时候,优化器会根据分区定义过滤那些没有我们需要数据的分区,这样查询就无须扫描所有分区。
分区的一个主要目的是将数据按照一个较粗的粒度分在不同的表中。
在下面的场景中,分区会起到很大的作用:
- 表非常大以至于无法全部放在内存中,或者只在表的最后部分有热点数据,其他均是历史数据。
- 分区表的数据更容易维护。
- 分区表的数据可以分布在不同的物理设备上,从而高效地利用多个硬件设备。
- 可以使用分区表来避免某些特殊的瓶颈。
- 如果需要还可以备份和恢复独立的分区,这在非常大的数据集的场景下效果非常好。
分区表本身也有一些限制:
- 一个表最多只能有1024个分区。
- 在Mysql5.1中分区表达式必须是整数,或者是返回整数的表达式。在Mysql5.5中,某些场景下可以直接使用列来进行分区。
- 如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进行。
- 分区表中无法使用外键约束。
当查询一个分区表的时候,分区层先打开并锁住所有的底层表,优化器先判断是否可以过滤部分分区,然后再调用对应的存储引擎接口访问各个分区的数据。
当写入一条记录时,分区层先打开并锁住所有的底层表,然后确定哪个分区表接收这条记录,再将记录写入对应底层表。
当删除一条记录时,分区层先打开并锁住所有的底层表,然后确定数据对应的分区,最后对相应底层表进行删除操作。
当更新一条记录时,分区层先打开并锁住所有的底层表,Mysql先确定需要更新的记录在哪个分区,然后取出数据并更新,在判断更新后的数据应该放在哪个分区,最后底层表进行写入操作,并对原数据所在的底层表进行删除操作。
虽然每个操作都会先打开并锁住所有的底层表,但这并不是说分区表在处理过程中是锁住全表的。如果存储引擎能够自己实现行锁,则会在分区层释放对应表锁。
Mysql支持多种分区表,最多的是根据范围进行分区,每个分区存储落在某个范围的记录。分区表达式可以是列,也可以是包含列的表达式。
CREATE TABLE sales (
order_date DATETIME NOT NULL,
name VARCHAR NULL DEFAULT ''
) ENGINE=InnoDB PARTITION BY RANGE(YEAR(order_date)) (
PARTITION p_2021 VALUES LESS THAN (2021),
PARTITION p_2022 VALUES LESS THAN (2022),
PARTITION p_2023 VALUES LESS THAN (2023),
PARTITION p_catchall VALUES LESS THAN MAXVALUE
);
PARTITION 分区子句中可以使用各种函数,但有一个要求表达式返回的值要是一个确定的整数,且不能是一个常数。
我们可以将分区当做索引的最初形态,以代价非常小的方式定位到需要的数据在哪一片区域。在这片区域中可以做顺序扫描,可以建索引,还可以将数据缓存到内存等等。因为分区无须额外的数据结构记录每个分区有哪些数据,所以代价非常低,只需要一个简单的表达式就可以表达每个分区存放的是什么数据。
为了保证大数据量的可扩展性,一般有两个策略:
- 全量扫描数据,不要任何索引
- 索引数据,并分离热点
并且分区实现中也有一些其他限制:
- 所有分区都必须使用相同的存储引擎
- 分区函数中可以使用的函数和表达式也有一些限制
- 某些存储引擎不支持分区
- 对于MyISAM的分区表,不能再使用 LOAD INDEX INTO CACHE 操作
- 对于MyISAM表,使用分区表时需要打开更多的文件描述符
分区最大的优点就是优化器可以根据分区函数来过滤一些分区,根据粗粒度索引的优势,通过分区来过滤通常可以让查询扫描更少的数据。
合并表时一种早期的、简单的分区实现,和分区表相比有一些不同的限制,并且缺乏优化。分区表严格来说是一个逻辑上的概念,用户无法访问底层的各个分区。但是合并表允许用户单独访问各个子表。分区表和优化器的结合更紧密,这也是未来发展的趋势,而合并表则是一种将淘汰的技术。
视图本身是一个虚拟表,不存放任何数据。在使用SQL语句访问视图的时候,他返回的数据是Mysql从其他表中生成的。视图和表是在同一个命名空间。
可更新的视图是指可以通过更新这个视图来更新视图涉及的相关表。只要指定了合适的条件,就可以更新、删除甚至向视图中写入数据。如果视图中定义了包含 GROUP BY 、UNION、聚合函数,以及其他一些特殊情况,就不能被更新了。更新视图的查询也可以是一个关联语句,但是有一个限制,被更新的列必须来自同一个表。
视图可以和其他提升性能的方式叠加使用。例如在重构schema的时候可以使用视图,使得在修改视图底层表结构的时候,应用代码还能继续不报错的运行。
InnoDB是目前Mysql中唯一支持外键的内置存储引擎。
使用外键是有成本的。比如外键通常都要求每次修改数据时都要在另外一张表中多执行一次查找操作。虽然InnoDB强制外键使用索引,但还是无法消除这种约束检查的开销。如果外键列的选择性很低,则会导致一个非常大且选择性很低的索引。
Mysql允许通过触发器、存储过程、函数的形式来存储代码。
Mysql中使用存储代码的优点:
- 在服务器内部执行,离数据最近,另外在服务器上执行还可以节省带宽和网络延迟。
- 这是一种代码重用。
- 可以简化代码的维护和版本更新。
- 可以帮助提升安全,比如提供更细粒度的权限控制。
- 服务端可以缓存存储过程的执行计划,这对于需要反复调用的过程,会大大降低消耗。
- 因为是在服务器端部署,所以备份、维护都可以在服务器端完成。
- 可以在应用开发和数据库开发人员之间更好地分工。
Mysql中使用存储代码的缺点:
- Mysql本身没有提供好用的开发和调试工具。
- 较之应用程序的代码,存储代码效率要稍微差些。
- 存储代码可能会给应用程序代码的部署带来额外的复杂性。
- 因为存储代码都是部署在服务器内,所以可能有安全隐患。
- 存储过程会给数据库服务器增加额外的压力。
- Mysql并没有什么选项可以控制存储程序的资源消耗,所以在存储过程中一个小错误都可能直接把服务器拖死。
- 存储代码在Mysql中的实现也有很多限制。
- 调试Mysql的存储过程比较困难。
Mysql的架构本身和优化器的特性使得存储代码有一些天然的限制:
优化器无法使用关键字 DETERMINISTIC 来优化单个查询中多次调用存储函数的情况。
优化器无法评估存储函数的执行成本。
每个连接都有独立的存储过程的执行计划缓存。如果有多个连接需要调用同一个存储过程,将会浪费缓存空间来反复缓存同样的执行计划。
存储程序和复制时一张诡异组合。
触发器可以让我们在执行 INSERT、UPDATE 或者 DELETE 的时候,执行一些特地的操作。可以在Mysql中指定是在SQL语句执行前触发还是在执行后触发。触发器本身没有返回值,不过他们可以读取或者改变触发SQL语句所影响的数据。因为触发器可以减少客户端和服务端之间的通信,所以触发器可以简化应用逻辑,还可以提高性能。
事件是Mysql5.1引入的一种新的存储代码的方式。我们可以创建一个事件,指定Mysql在某个时刻执行一段SQL代码,或者每隔一个时间间隔执行一段SQL代码。
Mysql在服务器端提供只读的、单向的游标,而且只能子存储过程或者更底层的客户端API中使用。因为Mysql游标中指向的对象都是存储在临时表中而不是实际查询到的数据,所以Mysql游标总是只读的。
Mysql使用绑定变量能够高效执行大量重复语句的原因:
- 在服务器端只需要解析一次SQL语句。
- 在服务器端某些优化器的工作只需要执行一次。
- 以二进制的方式只发送参数和句柄。
- 仅仅需要发送参数到服务器端,所以网络开销会很小。
- Mysql在存储参数的时候,直接将其存放到缓存中,不再需要再内存中多次复制。
对使用绑定变量的SQL,Mysql能够缓存其部分执行计划,如果某些执行计划需要根据传入的参数来计算的话,Mysql就无法缓存这部分的执行计划。
字符集是指一种从二进制编码到某类字符符号的映射。每一类编码字符都有其对应的字符集和校对规则。
每种字符集都可能有多种校对规则,并且都有一个默认的校对规则。每个校对规则都是针对某个特定的字符集的,和其他的字符集没有关系。校对规则和字符集总是一起使用的。
Mysql的设置可以分为两类:创建对象时的默认值、在服务器和客户端通信时的设置。
Mysql服务器有默认的字符集和校对规则,每个数据库也有自己的默认值,每个表也有自己的默认值。在这个“阶梯”的每一层,都可以指定一个特定的字符集或者让服务器使用他的默认值:
- 创建数据库的时候,将根据服务器上的 character_set_server 设置来设定数据库的默认字符集
- 创建表的时候,将根据数据库的字符集设置指定表的默认字符集。
- 创建列的时候,将根据表的设置指定列的默认字符集。
当服务器和客户端通信的时候,他们可能使用不同的字符集,这是服务器端需要进行必要的翻译转换工作:
服务器端总是假设客户端是按照 character_set_client 设置的字符集来传输数据和SQL语句的。
当服务器收到客户端的SQL语句时,先将其转换成字符集 character_set_connection 。
当服务器端返回数据或者错误信息给客户端时,他会将其转换成 character_set_result。
如果比较的两个字符串的字符集不同,Mysql会先将其转换成同一个字符集再进行比较。
对于校对规则的选择通常需要考虑一个问题,是否以大小写敏感的方式比较字符串, 或者是以字符串编码的二进制值来比较大小。
某些字符集和校对规则可能会需要更多的CPU操作,可能会消耗更多的内存和存储空间,甚至还会影响索引的正常使用。
通过数值比较、范围过滤等就可以完成绝大多数我们需要的查询了。但是,如果希望通过关键字匹配来进行查询过滤,那么就需要基于相似度的查询了,全文索引就是为这种场景设计。
全文索引可以支持各种字符内容的搜索包括CHAR、VARCHAR 和 TEXT ,也支持自然语言搜索和布尔搜索。
MyISAM对全文索引的支持有很多的限制,例如表级锁对性能的影响、数据文件的崩溃、崩溃后的恢复等等,这使得MyISAM的全文索引对于很多应用场景并不合适。
自然语言搜索引擎将计算每个文档对象和查询的相关度。相关度是基于匹配的关键词个数,以及关键词在文档中出现的次数。在整个索引中出现次数越少的词语,匹配时的相关度就越高。
存储引擎的事务特性能够保证在存储引擎级别实现ACID,而分布式事务则让存储引擎级别的ACID可以扩展到数据库层面,甚至可以扩展到多个数据库之间。这需要两阶段提交实现。
XA事务中需要有一个事务协调器来保证所有的事务参与者都完成了准备工作。如果协调器收到所有的参与者都准备好的消息,就会告诉所有的事务可以提交了。Mysql在XA事务中扮演一个参与者的角色,而不是协调者。
Mysql本身的插件式架构导致在其内部需要使用XA事务。Mysql中各个存储引擎是完全独立的,所以一个跨存储引擎的事务就需要一个外部的协调者。
Mysql能够作为参与者完成一个外部的分布式事务,但他对XA协议支持并不完整,例如,XA协议要求在一个事务中的多个连接可以做关联。
Mysql查询缓存保存查询返回的完整结果。当查询命中该缓存,Mysql会立刻返回结果,跳过了解析、优化和执行阶段。
查询缓存系统会跟踪查询中涉及的每个表,如果这些表发生变化,那么和这个表相关的所有的缓存数据都将失效。这种机制效率看起来比较低,因为数据表变化时很有可能对应的查询结果并没有变更。
Mysql判断缓存命中的方法很简单:缓存存放在一个引用表中,通过一个哈希值引用,这个哈希值包括了如下因素,即查询本身、当前要查询的数据库、客户端协议的版本等一些其他可能会影响返回结果的信息。
当判断缓存是否命中时,Mysql不会解析或者参数化查询语句,而是直接使用SQL语句和客户端发生过来的其他原始信息。任何字符上的不同,例如空格、注释等任何的不同都会导致缓存的不命中。
查询缓存是完全存储在内存中的,所以在配置和使用他之前,我们需要先了解他是如何使用内存的。除了查询结果之外,需要缓存的还有很多别的维护相关的数据。
并不是什么情况下查询缓存都会提高系统性能。缓存和失效都会带来额外的消耗,所以只有当缓存带来的资源节约大于其本身的资源消耗时才会给系统带来性能提升。
一旦理解查询缓存工作的原理,配置起来就容易多了。
query_cache_type:是否打开查询缓存,可以设置成OFF、ON或DEMAND。DEMAND表示只有在查询语句中明确写明SQL_CACHE的语句才放入查询缓存。
query_cache_size:查询缓存使用的总内存空间。
query_cache_min_res_unit:在查询缓存中分配内存块时的最小单位。
query_cache_limit:Mysql能够缓存的最大查询结果。如果查询结果大于这个值则不会被缓存。
query_cache_wlock_invalidate:如果某个数据表被其他连接锁住,是否仍然从查询缓存中返回结果。