Mysql 高级特性

注:本篇是《高性能Mysql》第三版的读书笔记

 

分区表

根据某种情况进行分表操作,一般常用于按照时间分表,比如每个月的数据存储到一张分区表中,分区表在查询时匹配where条件,查询总表时匹配where条件会减少查询范围,去查询分区表,过滤很多其他分区表的数据来达到一个高效的方式。

在MySQL中使用关键字 partition 

在postgresql中使用关键字 inherits / inhert

 

分区表的目的基本是数据量太大用来优化查询速度的,或者热点数据。

千万级的数据就可以考虑使用分区表了,尤其对于某种属性有特性的,比如按照时间会进行膨胀的表,可以按天或者按月分个表就美滋滋了。

 

分区需要注意的

  1. NULL值会使分区的过滤无效。(当然这个一般不会出现,分区键肯定会设置成not null 的)
  2. 分区列和索引列不匹配。(索引和列不匹配会导致不走索引,这不是废话)
  3. 分区的数量要有数,一般100个分区就是极限了。
  4. 打开并锁住所有底层表的成本可能很高。(当访问分区表的时候,会打开并锁住所有的底层表,这是另一个开销。无法通过过滤来规避)
  5. 维护的程度。在插入的时候需要写一些function
  6. 所有的分区需要使用相同的存储引擎。
  7. 有些存储引擎不支持分区,当然innodb肯定是支持的。
  8. 在 MySQL 中,创建分区表时候有一个限制条件:分区表中的每个唯一索引(包括主键),都必须包含分区表达式中的所有列。

分区的语句:

CREATE TABLE test (
    id int(8) not null,
    username VARCHAR(20) NOT NULL,
    created DATETIME NOT NULL,
    PRIMARY KEY(id, created)
)
PARTITION BY RANGE( YEAR(created) )(
    PARTITION from_2013_or_less VALUES LESS THAN (2014),
    PARTITION from_2014 VALUES LESS THAN (2015),
    PARTITION from_2015 VALUES LESS THAN (2016),
    PARTITION from_2016_and_up VALUES LESS THAN MAXVALUE);


INSERT into test(id,username,created) values(1,'test','2013-01-01');
INSERT into test(id,username,created) values(2,'test','2014-01-01');
INSERT into test(id,username,created) values(3,'test','2015-01-01');
INSERT into test(id,username,created) values(4,'test','2016-01-01');
INSERT into test(id,username,created) values(5,'test','2017-01-01');


explain select * from test where created<'2015-01-01' and created > '2014-01-01'; #使用范围查询
explain select * from test where created<'2015-01-01' ; # 返回2013和2014两个分区表

 


DDL可以看到如下
CREATE TABLE `test` (
  `id` int(8) NOT NULL,
  `username` varchar(20) NOT NULL,
  `created` datetime NOT NULL,
  PRIMARY KEY (`id`,`created`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/*!50100 PARTITION BY RANGE ( YEAR(created))
(PARTITION from_2013_or_less VALUES LESS THAN (2014) ENGINE = InnoDB,
 PARTITION from_2014 VALUES LESS THAN (2015) ENGINE = InnoDB,
 PARTITION from_2015 VALUES LESS THAN (2016) ENGINE = InnoDB,
 PARTITION from_2016_and_up VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */;

 

存储过程

mysql的存储过程包括几部分。可以参照下面一个授权取数据的小需求来参考。(暂时不关注sql的性能问题)

DELIMITER //                                                  #这个是定义执行语句的分隔符。  如果不改的话默认是 ; 分割。在mysql客户端执行是有问题的。会导致识别存储过程的函数体异常。
CREATE PROCEDURE get_module_sn_data_pro(in _name text)
begin                                                              #这个是声明函数体的开始
declare total_name text;
declare var_name_index int(5);                      #声明变量时给变量的类型存储大小
declare var_name_value text;                        
declare module_name text;
declare sql_name text;
declare tmp_sql text;
declare tmp_var int(3) default 1;                    #声明变量的时候赋一个默认值。
set total_name = concat(_name,',tmp_value');      #使用concat来进行字符串连接操作   
while LOCATE(',',total_name) != 0 do
set var_name_value = (select SUBSTRING(total_name,1,INSTR(total_name,',')-1) );
set var_name_index = (select INSTR(total_name,','));
set module_name = var_name_value;
set tmp_sql = concat("(SELECT c_endtime,
c_functional -> '$.",module_name,".authorized_time' AS mod_time                                         #一种mysql使用字符串存储json的手段
" FROM test WHERE
c_functional !='NULL' and RIGHT(c_functional,1)='}' and c_endtime> '2019-12-01 00:00:00';
if tmp_var = 1 then
set sql_name = tmp_sql;
else
set sql_name = concat(sql_name," union all ", tmp_sql);
end if;
set tmp_var = 0;
set total_name = SUBSTRING(total_name,var_name_index+1);
end while;
set @get_module_sn_sql = concat(sql_name);
select @get_module_sn_sql;                                               #第一个结果集输出的是sql,第二个结果集是真正使用的sql
PREPARE stmt FROM @get_module_sn_sql ;                      #预编译一个sql   statement  
EXECUTE stmt;                                                                     #执行这个sql (可以这么理解,因为底层逻辑可能会深一些)
end                                                                                      #声明函数体的结束
//                                                                                         # 最开始定义的 // 为mysql客户端执行命令的结束符号。
DELIMITER ;                                                                         #跑完存储过程的创建函数后把这个声明给改回来。

删掉存储过程

drop PROCEDURE if exists get_module_sn_data_pro;

执行存储过程

call get_module_sn_data_pro("qwe,rty");  #括号里面是参数不需要管

有关mysql存储过程的一些坑

1.declare test_name varchar(30) ; 必须有这个长度。   这个是使用 declare声明变量的时候需要注意的事情。

2.给变量赋值要是左边是sql 必须用() 扩起来。            这个是给变量赋值的时候需要注意的事情。

3.尽量使用declare这种作用域小的声明变量方式。       这个没遇到坑。但是感觉大量使用@这种会话变量会有一些坑

mysql存储过程中,定义变量有两种方式:
1.使用set或select直接赋值,变量名以 @ 开头.
例如:set @var=1;
可以在一个会话的任何地方声明,作用域是整个会话,称为会话变量。会话变量作用域会暴露、

2.以 DECLARE 关键字声明的变量,只能在存储过程中使用,称为存储过程变量,例如:
DECLARE var1  INT DEFAULT 0;  
主要用在存储过程中,或者是给存储传参数中。

两者的区别是:
在调用存储过程时,以DECLARE声明的变量都会被初始化为 NULL。而会话变量(即@开头的变量)则不会被再初始化,在一个会话内,只须初始化一次,之后在会话内都是对上一次计算的结果,就相当于在是这个会话内的全局变量。

在存储过程中,使用动态语句,预处理时,动态内容必须赋给一个会话变量。

 

触发器则是某个insert  update  delete执行了会触发。

 

MySQL如何使用字符集

只有基于字符的存储才跟字符集有关系,其他类型跟字符集没啥关系。

MySQL的设置可以分为两类:创建对象时的默认值、在服务器和客户端通信时的设置。

 

MySQL服务器有默认的字符集和校验规则,每个数据库也有自己的默认值,每个表也有自己的默认值,每个列也有默认值。这是一个逐层继承的默认设置。

  1. 创建数据库时根据服务器上的character_set_server来设置该数据库的默认字符集。
  2. 创建表的时候,将根据数据库的字符集设置来指定这个表的字符集设置。
  3. 创建列的时候,根据表的设置指定列的字符集设置。

当列没有设置字符集的时候才会继承表的字符集。

  1. 服务端总是假定客户端是根据character_set_client设置的字符集来传输数据sql语句的。  后台和MySQL的传输!
  2. 当服务器收到客户端的sql语句时,它先将其转换成character_set_connection 它还使用这个设置来决定如何将数据转换成字符串。
  3. 当服务器端返回数据或者错误信息给客户端是,他会将其转换成character_set_result

有的需要去配置文件修改。

MySQL再比较两个字符的时候,即使不相同的字符集会转换成相同的字符集进行比较,如果转换失败就抛异常了。

可以通过convert函数转码字符集。

 

选择字符集和校对规则

对于校对规则通常需要考虑的一个问题是,是否以大小写敏感的方式比较字符串,或者是以字符串编码的二进制值来比较大小。

对应得校对规则的前缀是_cs、ci、_bin 

 

全文索引

mysql5.6之前是只有myisam支持全文索引,5.6之后的innodb也支持全文索引了。

innodb存储引擎创建全文索引如下所示,选择fulltext全文索引不能选b-tree和hash等别的索引方式。

ALTER TABLE pressure ADD FULLTEXT INDEX fulltext_pre(hobby);

全文索引的性能是比较差的,我实际项目开发中还没有使用过MySQL的全文索引

 

分布式(XA)事务

存储引擎的事务特性能保证在存储引擎级别实现ACID,而分布式事务可以让存储引擎级别的ACID扩展到数据库层面。分两个阶段

  1. XA事务需要有一个事务协调器来保证所有的事务参与者都完成了准备工作。
  2. 如果协调器收到所有的参与者都准备好的消息,就会告诉所有的事务可以提交了。

分布式事务分为内部XA事务和外部XA事务。

内部XA事务

例如:存储引擎和写入日志文件。这就是一个分布式事务。

XA事务为Mysql带来了巨大的性能下降,从5.0开始,它破坏了MySQL内部的批量提交。使得MySQL不得不多次调用fsync

一个事务如果开启了二进制日志,则不仅对二进制日志进行持久化操作,innodb事务日志还需要两次日志持久化操作。

外部XA事务

MySQL能够作为一个参与者完成一个外部的分布式事务。因为通信的延迟,外部XA事务消耗更大。因为一个参与者阻塞会导致其他参与者等待。

XA事务是一种在多个服务器之间同步数据的方法,如果由于某些原因不能使用MySQL本身的复制,或者性能并不是瓶颈的时候,可以尝试使用。

 

查询缓存

很多数据库产品都能够缓存查询的执行计划,对于相同类型的SQL就可以跳过SQL解析和执行计划生成阶段。

MySQL还有一种不同的缓存类型:缓存完整的select查询结果,也就是查询缓存

MySQL查询缓存保存查询返回的完整结果,当查询命中改缓存,MySQL会立刻返回结果,跳过了解析、优化和执行阶段。

查询缓存会跟踪查询中涉及的每个表,如果这些表发生变化,那么和这个表相关的所有缓存数据都将失效。“这种简单实现代价很小,对于一个繁重的系统来说非常重要”

随着现在通用服务器越来越强大,查询缓存被发现是一个影响服务器扩展性的因素。它可能成为整个服务器资源竞争单点,在多核服务器上还可能导致服务器僵死。

大部分认为应该默认关闭查询缓存,如果很需求的话就配置一个很小的查询缓存空间(几十兆)

query_cache_type 字段是判断是否打开查询缓存的配置。

MySQL如何判断缓存命中?

缓存存放在一个引用表中,通过一个哈希值引用,这个哈希值包括:查询本身,当前要查询的数据库,客户端版本协议等。

当判断缓存是否命中时,MySQL不会解析、正规化或者参数化查询语句,而是直接使用SQL语句和客户端发送过来的其他原始信息。

  1. 任何字符上的不同例如:空格,注释---任何的不同都会导致缓存的不命中。所以使用统一的编码是很有必要的。
  2. 当查询有一些不确定的值时不会被缓存例如:now() 
  3. 当查询中包含任何用户自定义函数,存储函数,用户变量,临时表,MySQL中的系统表或者任何包含列级别权限的表,都不会被缓存。
  4. 当查询缓存可使用内存满了,MySQL就会清除一些查询缓存结果。

在检查查询缓存之前,MySQL只做一件事情,通过一个不区分大小写的检查看SQL语句是不是以sel开头。select

tips: 如果查询缓存使用了大量的内存,缓存失效操作就可能成为一个非常严重的问题瓶颈。如果缓存存放了大量的结果,缓存失效操作时会导致整个系统僵死一会儿,因为这个操作是靠一个全局锁操作保护的,所有需要做该操作的查询都要等待这个锁,而且无论是检查是否命中缓存、还是缓存失效检测都需要等待这个全局锁。

 

查询缓存如何使用内存?

查询缓存是完全存储在内存中的,在服务器启动的时候会初始化查询缓存需要使用的内存空间。

当有查询结果需要缓存的时候,MySQL从大空间块申请一个数据块用于存储结果。这个数据块需要大于参数query_cache_min_res_unit的配置。需要一步步从小到大分配给查询缓存结果直到数据块足够大。小的存满,新申请大的继续存。这其中的分配是MySQL管理的。

tips: 对于多个查询缓存来说,很容易产生内存碎片。碎片的大小小于query_cache_min_res_unit ,无法被新的查询缓存来存储结果。

需要内存回收机制来回收内存碎片。减少碎片可以通过对query_cache_min_res_unit参数的调小。

flush query cache来清碎片。不会清查询缓存结果。会进行重排序。

 

MySQL接到一个select查询的时候,要么增加Qcache_hits的值,要么增加Com_select的值。

查询缓存命中率: Qcache_hits/(Qcache_hits+Com_select) 

 

综上:使用查询缓存要保证占用内存小,并且足够大的收益。

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章