Lin Hong's TECH Blog! 刀不磨要生锈,人不学习要落后 - Thinking ahead

MySQL 8.0 Study 013 Tips

2023-02-21

MySQL 8.0 Study 013 Tips

学习系列

  • MySQL GTID
  • MySQL 故障场景分析
  • MySQL 导入导出文件(secure_file_priv)
  • MySQL 优化之执行计划
  • MySQL 被攻击后的措施

MySQL GTID

什么是GTID

GTID基本概念

  • GTID(全局事务ID)是MySQL在5.6加入的特性,MySQL会为每一个DDL/DML操作增加一个唯一标记,叫做GTID。主库可以直接通过GTID定位要发送的binlog位置,不再需要像传统复制一样指定文件和位点。
  • GTID由UUID和NUMBER组成,UUID存储在auto.cnf文件中,也可以通过select @@server_uuid查到,NUMBER为GTID序号。
mysql [localhost:8033] {msandbox} (test) > show variables like '%uuid%';
+---------------+--------------------------------------+
| Variable_name | Value                                |
+---------------+--------------------------------------+
| server_uuid   | 00008033-0000-0000-0000-000000008033 |
+---------------+--------------------------------------+
1 row in set (0.01 sec)

mysql [localhost:8033] {msandbox} (test) > 
GTID事务:由全局事务标识符(GTID)标识,格式为UUID:NUMBER。
在binlog日志中的每个GTID的事务总是在BEGIN标记之前(Query log event 的之前)加上一个Gtid_log_event。
GTID事务可以使用GTID来进行自动查找事务,也可以使用binlog file和pos进行寻址。
匿名事务:没有分配GTID,MySQL 5.7.6及更高版本在binlog日志中的每个匿名事务总是在begin标记之前(Query log event的之前)加上一个 Anonymous_gtid_log_event,在这个Event中使用了SET @@SESSION.GTID_NEXT= 'ANONYMOUS'语句。
在以前的版本中,匿名事务没有这个Anonymous_gtid_log_event。只能使用binlog file和pos来解决。

相关参数

gtid 参数:

mysql [localhost:8033] {msandbox} (test) > show variables like '%gtid%';
+----------------------------------+-----------+
| Variable_name                    | Value     |
+----------------------------------+-----------+
| binlog_gtid_simple_recovery      | ON        |
| enforce_gtid_consistency         | OFF       |
| gtid_executed                    |           |
| gtid_executed_compression_period | 0         |
| gtid_mode                        | OFF       |
| gtid_next                        | AUTOMATIC |
| gtid_owned                       |           |
| gtid_purged                      |           |
| session_track_gtids              | OFF       |
+----------------------------------+-----------+
9 rows in set (0.00 sec)

mysql [localhost:8033] {msandbox} (test) > 

说明:

binlog_gtid_simple_recovery
#此变量控制在MySQL启动或重新启动时,在搜索二进制日志中的GTID期间如何迭代扫描二进制日志文件
当binlog_gtid_simple_recovery = FALSE时,迭代扫描二进制日志文件的方法是:
初始化gtid_executed系统变量:从新的二进制文件倒推迭代扫描,直到匹配到二进制日志中第一个具有Previous_gtids_log_event事件且该事件中包含非空的GTID集合时停止迭代扫描。然后使用来自该二进制日志文件中的所有的Previous_gtids_log_event和Gtid_log_events事件中的GTID生成一 个GTID集合,该集合被用于设置 gtid_executed系统变量的值以及更新mysql.gtid_executed表中的GTID结合。如果倒推迭代扫描的路径上的二进制日志中有大量不具备 GTID的事务(匿名事务),那么这个迭代过程可能需要很长时间,例如在 GTID复制转换到传统复制模式之后。注意:如果所有的binlog中都没有找到 具有GTID的事务的事件,则该变量被设置为空串   

初始化gtid_purged系统变量:从旧的二进制日志到新的二进制日志顺序迭代扫描,直到匹配到二进制日志中第一个具有 Previous_gtids_log_event事件且该事件中包含非空的GTID集合时或者至少有一个Gtid_log_event事件时停止迭代扫描。然后从这个二进制日志读取 Previous_gtids_log_event生成GTID集合。再使用内部变量gtids_in_binlog中保存的GTID集合减去该GTID集合,把计算结果存储在内部变量 gtids_in_binlog_not_purged中。gtid_purged的值被初始化为gtid_executed的值减去gtids_in_binlog_not_purged。注意:如果所有的binlog中都没有找 到具有GTID的事务的事件,则该变量被设置为空串 

当binlog_gtid_simple_recovery = TRUE时,mysql仅迭代扫描旧的和新的二进制日志文件,而 gtid_purged和gtid_executed的值仅基于这些文件中的 Previous_gtids_log_event或Gtid_log_event计算。这使得在mysqld重新启 动期间或清除二进制日志时,只会迭代扫描两个二进制日志文件。加快了扫描速度。

* 新的二进制日志是由MySQL 5.7.5或更早版本生成的。对于某些二进制日志开启了GTID,但新的二进制日志又关闭了GTID
* 在5.7.7之前的MySQL版本上使用SET GTID_PURGED语句且在执行SET GTID_PURGED时指定的需要清理的二进制日志并没有完全清除成功   * 如果在任一情况下导致计算出不正确的GTID集合,即使后续重新启动服务 器,也会保持不正确,无论该系统参数设置为何值,需要先手工清理那些可 能导致计算不正确值的binlog或者更新数据库版本到5.7.7及其之后的版本

enforce_gtid_consistency
#是否启用强制GTID一致性
设置为true时,服务器通过只执行可以以事务安全方式记录的那些语句来强制执行GTID一致性。在使用gtid_mode=ON时,需要把该参数一并设置 为ON。
由于只有当enforce_gtid_consistency为true时才会记录事务安全语句,因此,使用GTID复制时不支持以下操作:
* 不支持CREATE TABLE ... SELECT语句
* 不支持在事务内使用CREATE TEMPORARY TABLE语句(5.7开始支持临时表)
* 不支持在一个事务或语句内同时更新事务表和非事务表的语句

gtid_executed_compression_period
#表示mysql server在执行了该变量指定的事务数量之后,执行一次压缩mysql.gtid_executed表,如果设置为0则表示不压缩mysql.gtid_executed表
由于在使用二进制日志功能时,mysql.gtid_executed表不会实时记录从库同步的主库事务的GTID,所以也就不会发生表的压缩,即在开启binlog时该变量不起作用,除非禁用二进制日志记录时,该表中才会实时记录从库同步的主库事务的 GTID,所以此时压缩功能才生效。

gtid_mode
#是否开启gtid复制模式,该参数有如下几个有效值
在MySQL 5.7.6以前,gtid_mode是只读参数,修改值只能通过重启实例生效。
在MySQL 5.7.6开始,gtid_mode支持在线动态修改。
在MySQL里事务分为两种,一种是匿名事务(anonymous),一种是GTID事务。GTID事务即该事务带有GTID信息,而匿名事务即使没有GTID信息的事务,在主从复制中只能指定binlog文件和位点的方式来搭建。
从MySQL 5.7.6开始,gtid_mode有以下四个值:
OFF,主库所有新启的事务以及从库的事务都要求是匿名事务;例如主库发起事务A,那么这个事务没有GTID信息,相关binlog传到从库,由从库复制该事务时,从库开启的这个事务也必须是匿名的,从库的gtid_mode也应该是OFF或OFF_PERMISSIVE;
OFF_PERMISSIVE,主库新启的事务是匿名事务,但从库事务允许是匿名的或者是GTID事务;即从库的gtid_mode需要是ON_PERMISSIVE或OFF_PERMISSIVE或OFF;
ON_PERMISSIVE,主库新启的事务是GTID事务,从库事务允许是匿名的或者是GTID事务;即从库的gtid_mode需要是ON或ON_PERMISSIVE或OFF_PERMISSIVE;
ON,主库新启的事务是GTID事务,从库的事务也要求是GTID事务,即若主为ON,那么从库的gtid_mode应该要ON_PERMISSIVE或ON。

各值兼容规律:
ON,ON_PERMISSIVE,OFF_PERMISSIVE,OFF
按以上顺序,主库确定某个取值,那么从库只能取相同的值,或者该值临近两侧的值。
例如主库是ON_PERMISSIVE,那么从库可以是ON,ON_PERMISSIVE,OFF_PERMISSIVE,但不能是OFF;主库是ON,那么从库可以是ON,ON_PERMISSIVE,但不能是OFF_PERMISSIVE,OFF。

gtid_next
#在GTID复制架构中,用于指定如何获取GTID号,该变量要生效需要依赖于gtid_mode=on,如果 gtid_mode为OFF,则设置此变量不起作用
* AUTOMATIC:使用下一个自动生成的全局事务ID。   
* ANONYMOUS:事务不具有全局标识符,仅由binlog file和binlog pos标识 (在MySQL 5.7版本中,该类型事务在binlog中多了一个 Anonymous_gtid_log_event来标记匿名事务,在这个Event中使用了SET @@SESSION.GTID_NEXT= 'ANONYMOUS'语句)   
* UUID中的全局事务ID:NUMBER格式。即,手工指定一个GTID号,手工 指定GTID常用语在复制发生错误时,手工跳过发生错误的事务GTID(手工 指定后,通过执行begin;commit;语句来提交一个空事务使其GTID号增长到 指定的GTID位置),手工指定的事务号提交或回滚之后,需要把该变量的 值再次修改回AUTOMATIC,并重新启动复制 会话变量,动态变量,枚举类型,有效值为:AUTOMATIC、 ANONYMOUS、手工指定一个GTID,默认值为AUTOMATIC 
注意:在MySQL 5.7.5 之前的版本中(5.6.x 在 MySQL 5.6.20 之前的版本中),当该变量值不为AUTOMATIC时,drop temporary table或者drop table与drop temporary table的组合使用场景会失败,这目前是一个BUG(#17620053)
* 在5.7.1版本或5.6.20版本中,在该变量不为AUTOMATIC时,无法执行 CHANGE MASTER TO, START SLAVE, STOP SLAVE, REPAIR TABLE, OPTIMIZE TABLE, ANALYZE TABLE, CHECK TABLE, CREATE SERVER, ALTER SERVER, DROP SERVER, CACHE INDEX, LOAD INDEX INTO CACHE, FLUSH, or RESET这些语句,这是一个BUG (#16062608, #16715809, #69045) (#16062608)

gtid_owned
#此变量保存的GTID集合值取决于其作用范围,当使用会话级别查看时,该变量显示包含该客户端的所有GTID集合; 当使用全局级别查看时,该变量显示server中 所有的GTID集合

gtid_purged
#该变量的值是记录所有已经从二进制日志中清除的事务集合。
该GTID集合是gtid_executed表中记录的所有事务集合的一个子集。当服务器启动时,gtid_purged的全局值被初始化为一组GTID(但如果是一 个从未清理过binlog的实例,这个变量可能一直为空)。
在MySQL 5.7.6之前,此变量 只有在gtid_mode = ON时才可设置。
在MySQL 5.7.6及更高版本中,无论 gtid_mode的值如何,此变量都是可设置的 在MySQL 5.7.6及其之前的版本产生的binlog,有可能在mysqld重启之后重 新计算gtid_purged系统变量时计算错误,可以使用系统变量 binlog_gtid_simple_recovery=false来避免计算错误,在MySQL 5.7.7及其之后的版本无此问题,可以直接设置binlog_gtid_simple_recovery = TRUE

RESET MASTER:
删除binlog索引文件中列出的所有binlog文件
清空binlog索引文件
创建一个新的binlog文件
清空系统变量gtid_purged和gtid_executed
在MySQL 5.7.5 及后续版本中, RESET MASTER还会会清空 mysql.gtid_executed数据表

RESET SLAVE:
清除slave 复制时的master binlog的位置
清空master info, relay log info
删除所有的relay log文件,并创建一个新的relay log文件。
重置复制延迟(CHANGE MASTER TO 的 MASTER_DELAY参数指定的)为0

session_track_gtids
控制服务器是否在当前会话中跟踪gtid并将其返回给客户端。根据变量值,在执行每个事务结束时,服务器gtid由跟踪器捕获并返回给客户机。允许使用这些session_track_gtids值:
OFF:追踪器不收集GTID。这是默认设置。
OWN_GTID:跟踪器收集成功提交的读/写事务生成的GTID。
ALL_GTIDS:跟踪器在当前事务提交时收集gtid执行的系统变量中的所有gtid,无论事务是读/写还是只读。
无法在事务上下文中设置会话跟踪ID。
MySQL从5.7.6开始可以在线切换
gtid-mode的几种状态说明:
off :不产生gtid,基于binlog+position,slave也不能接受gtid的日志;
off_permissive:不产生gtid,但做为slave可以识别gtid事务也可以识别非gtid事务;
on_permissive:产生gtid事务,slave可以处理gtid事务和非gtid事务;
on:产生gtid事务,slave只接受gtid事务。

传统--->GTID

1.所有的Server执行
set @@global.enforce_gtid_consistency = warn;
特别注意: 这一步是关建的一步使用不能出现警告。

2.所有的server上执行
set @@global.enforce_gtid_consistency = on;

3.所有的Server上执行(要执行完)
set @@global.gtid_mode = off_permissive;
set @@global.gtid_mode=on_permissive; #产生gtid的日志,这个步骤号称是不关心任何节点,但从管理上推荐在slave上先执行,然后再去master上执行。

4.传统的binlog复制完成确认
#显示已标记为匿名的正在进行的匿名事务的数量。
show status like 'ongoing_anonymous_transaction_count';
需要所有的节点都确认为0.
所有的节点也可以执行一下: flush logs; 用于切换一下日志。


5. 所有的节点启用gtid_mode
set @@global.gtid_mode=on;

6.配置文件修改
gtid_mode=on
enforce_gtid_consistency=on

7.启用gtid的自动查找节点复制
stop slave;
CHANGE MASTER TO  MASTER_HOST='127.0.0.1',MASTER_USER='repl',MASTER_PASSWORD='repl',MASTER_PORT=3306,MASTER_AUTO_POSITION=1;
start slave;



GTID--->传统
就是上面的反向过程
1.停止复制
stop slave;
show slave status\G;查看Exec_Master_Log_Pos的位置
change master to master_host='127.0.0.1',master_user='repl',master_password='repl',master_port=3306,master_log_file='mybinlog.000008',master_log_pos=80711;
start slave;

2.主库先更改日志格式,然后从库更改
set @@global.gtid_mode=on_permissive;
set @@global.gtid_mode=off_permissive;

3.
select @@global.gtid_owned;
要为空才正常

4.动态修改参数
set @@global.gtid_mode=off;
set @@global.enforce_gtid_consistency = off;

5.my.cnf修改

故障修复

故障修复

##传统复制##
#停止复制
mysql>stop slave;
#设定跳过一个事务
mysql>SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1
#重新开启复制
mysql>start slave;
#这样就正常了,但是,当然还是要把数据修改上去
mysql>update tables set ... where;
##GTID##
#停止复制
mysql>stop slave;
#然后执行设置一个事务GTID来跳过,就是要跳过这个事务的意思
mysql>SET gtid_next = '2a4aaa93-98e4-11ea-b9b2-00163e0c8a51:8';
#注入空事务
mysql>BEGIN;COMMIT;
#把GTID设置回自动模式
mysql>SET gtid_next = 'AUTOMATIC';
#重新开启复制
mysql>START SLAVE;
#当然跳过了,并不代表这个数据就不修改了,还是要你手动去修改一下,这样就一切回归正常了
mysql>update tables set ...;

#停止复制
mysql>stop slave;
#直接设置上面的GTID值+1
mysql>SET @@GLOBAL.GTID_PURGED='2a4aaa93-98e4-11ea-b9b2-00163e0c8a51:1-8';
#重新开启复制
mysql>START SLAVE;
#当然跳过了,并不代表这个数据就不修改了,还是要你手动去修改一下,这样就一切回归正常了
mysql>update tables set ...;

MySQL_MySQL_gtid01

MySQL_MySQL_gtid02

MySQL 故障场景分析

CPU高

数据库CPU高消耗一般来说主要有两点原因:

  • 1.慢SQL
    慢sql导致,比如说全表扫描、扫描数据量太大,排序操作,锁争用等。使用explain,profile进行优化
    
  • 2.QPS高
    大量并发导致资源紧张。
    

系统命令: top 查看系统CPU使用率情况,是否是MySQL导致。

MySQL 数据库层面:

# 查看QPS
show global status like 'Question%';
# 查看活跃会话
select * from information_schema.processlist where command!='Sleep';
# 查看逻辑读
show global status like 'innodb_buffer_pool_read_requests';
# 查看临时表情况
show status like 'Create_tmp%';
# 查看锁争用
SHOW STATUS LIKE ‘Innodb_row_lock%’;
# 引擎各种状态查看
show engine innodb status\G
# 分析慢日志
mysqldumpslow -s c slow.log>/tmp/slow_report.txt
根据以上状态值分析CPU高原因,针对具体问题具体分析。如:优化SQL,控制并发等。

IOPS高

系统命令: iostat 和pt-ioprofile查看IO性能 MySQL 数据库层面:

# 查看活跃会话
select * from information_schema.processlist where command!='Sleep';
# 查看数据库读写压力
SELECT file_name AS file,
    count_read,
    sum_number_of_bytes_read AS total_read,
    count_write,
    sum_number_of_bytes_write AS total_written,
    (sum_number_of_bytes_read + sum_number_of_bytes_write) AS total
 FROM performance_schema.file_summary_by_instance
ORDER BY sum_number_of_bytes_read+ sum_number_of_bytes_write DESC;
# 查看物理读
show global status like 'Innodb_buffer_pool_reads';

# 分析并优化相应SQL。

连接(活跃)数高

通过监控查看活跃连接数以及其他数据库资源问题,确定是由于大量活跃连接导致数据库资源打满还是资源打满导致活跃连接堆积。

# 查看当前活跃会话
select * from information_schema.processlist where command!='Sleep';

如果是大量高效SQL并发导致的问题,需要从业务上定位; 如果是低效SQL导致资源紧张,则需要根据活跃会话以及慢日志进行优化,临时解决可以通过kill会话处理。

磁盘空间

# 查看磁盘空间
df -Th
# 查看innode用量
df -iH
# 查看binlog占用空间
show master logs;
# 查看表大小
SELECT table_schema as `Database`, table_name AS `Table`, round(((data_length + index_length) / 1024 / 1024 / 1024), 2) `Size in GB` FROM information_schema.TABLES where table_schema = 'test' ORDER BY (data_length + index_length) DESC ;
# 查看临时表空间大小
select * from INFORMATION_SCHEMA.INNODB_TEMP_TABLE_INFO;

# 碎片查看
SELECT CONCAT(table_schema, '.', table_name)                    AS  TABLE_NAME
      ,engine                                                   AS  TABLE_ENGINE 
      ,table_type                                               AS  TABLE_TYPE
      ,table_rows                                               AS  TABLE_ROWS
      ,CONCAT(ROUND(data_length  / ( 1024 * 1024), 2), 'M')     AS  TB_DATA_SIZE 
      ,CONCAT(ROUND(index_length / ( 1024 * 1024), 2), 'M')     AS  TB_IDX_SIZE 
      ,CONCAT(ROUND((data_length + index_length ) 
            / ( 1024 * 1024 ), 2), 'M')                         AS  TOTAL_SIZE
      ,CASE WHEN  data_length =0 THEN 0
            ELSE  ROUND(index_length / data_length, 2) END      AS  TB_INDX_RATE
    ,CONCAT(ROUND( data_free / 1024 / 1024,2), 'MB')            AS  TB_DATA_FREE 
    ,CASE WHEN (data_length + index_length) = 0 THEN 0
             ELSE ROUND(data_free/(data_length + index_length),2) 
     END                                                        AS  TB_FRAG_RATE
FROM information_schema.TABLES  
ORDER BY data_free DESC;
#重建表空间
optimize table tablename;

复制延迟

MySQL 网络延迟:减小副本集节点之间的网络延迟。 磁盘吞吐量:保证磁盘吞吐。 查看

show slave status;

通过以上指标判断主从状态以及复制延迟,如果出现较大延迟,可以从以下几个方面考虑:

  • 大事物延迟,避免大事务
  • 大表DDL延迟,可能存在MDL锁阻塞,kill掉阻塞源
  • 长期未提交的事物延迟,杜绝长事务
  • 表上没有主键或者唯一键,可以通过slave_rows_search_algorithms变量一定程度优化,建议所有业务表均建立主键
  • Innodb层锁造成延迟,分析锁关系,做出相应处理

复制中断

根据复制中断做出相应调整,如果难以修复,建议重建从库

##传统复制##
#停止复制
mysql>stop slave;
#设定跳过一个事务
mysql>SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1
#重新开启复制
mysql>start slave;
#这样就正常了,但是,当然还是要把数据修改上去
mysql>update tables set ... where;
##GTID##
#停止复制
mysql>stop slave;
#然后执行设置一个事务GTID来跳过,就是要跳过这个事务的意思
mysql>SET gtid_next = '2a4aaa93-98e4-11ea-b9b2-00163e0c8a51:1-8';
#注入空事务
mysql>BEGIN;COMMIT;
#把GTID设置回自动模式
mysql>SET gtid_next = 'AUTOMATIC';
#重新开启复制
mysql>START SLAVE;
#当然跳过了,并不代表这个数据就不修改了,还是要你手动去修改一下,这样就一切回归正常了
mysql>update tables set ...;

#停止复制
mysql>stop slave;
#直接设置上面的GTID值+1
mysql>SET @@GLOBAL.GTID_PURGED='2a4aaa93-98e4-11ea-b9b2-00163e0c8a51:1-8';
#重新开启复制
mysql>START SLAVE;
#当然跳过了,并不代表这个数据就不修改了,还是要你手动去修改一下,这样就一切回归正常了
mysql>update tables set ...;

##传统模式##
#停止复制
mysql>stop slave;
#设定跳过一个事务
mysql>SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1
#重新开启复制
mysql>start slave;
#这样就正常了,但是,当然还是要把数据修改上去
mysql>update tables set ... where ;

数据丢失

DML误操作

  • 使用binlog2sql/myflash/脚本方式通过binlog做数据闪回恢复

DDL误操作

  • 建立延迟从库,基于全备+增备+binlog

数据库坏页

正常来讲,innodb_force_recovery=1即可,然后逻辑导出。

innodb_force_recovery=1,即使发现了损坏页面也继续让服务器继续运行,这个选项对于备份或者转存当前数据尤为有用
innodb_force_recovery=2,阻止恢复主线程的运行,如果清除操作会导致服务器挂掉
innodb_force_recovery=3,恢复后不回滚事务
innodb_force_recovery=4,如果插入到缓冲区的合并操作会导致系统崩溃,将不会被执行
innodb_force_recovery=5,启动数据库时,忽略撤消日志
innodb_force_recovery=6,启动数据库时,忽略与恢复相关的前滚日志

MySQL 导入导出文件(secure_file_priv)

#只读变量
#如果这个参数为空,这个变量没有效果; 
#如果这个参数设为一个目录名,MySQL服务只允许在这个目录中执行文件的导入和导出操作。这个目录必须存在,MySQL服务不会创建它; 
#如果这个参数为NULL,MySQL服务会禁止导入和导出操作。这个参数在MySQL 5.7.6版本引入
mysql [localhost:8033] {msandbox} (mysql) > show variables like '%secure_file_priv%';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| secure_file_priv | NULL  |
+------------------+-------+
1 row in set (0.01 sec)

mysql [localhost:8033] {msandbox} (mysql) > 

#以下操作需要file权限
select * from t1
into outfile '/tmp/test.csv'   
fields terminated by ','
optionally enclosed by '"'
escaped by '"'
lines terminated by '\r\n'; 


load data infile '/tmp/test.csv'   
into table test_t1   
fields terminated by ','  
optionally enclosed by '"' 
escaped by '"'   
lines terminated by '\r\n' (col1,col2); 

# 测试
select host,user from user into outfile '/tmp/mysql.user.csv' fields terminated by ',' optionally enclosed by '"' escaped by '"' lines terminated by '\r\n';

use test;

create table t_user as select host,user from mysql.user where 1<0;

load data infile '/tmp/mysql.user.csv'   
into table t_user   
fields terminated by ','  
optionally enclosed by '"' 
escaped by '"'   
lines terminated by '\r\n' (host,user); 

# 取出全部列名

SELECT GROUP_CONCAT(COLUMN_NAME order by ORDINAL_POSITION) FROM information_schema.COLUMNS  WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME = 't_user';

MySQL 优化之执行计划

一、优化特性

1.1 ICP(Index Condition Pushdown)
Index First Key
  只是用来定位索引的起始范围,因此只在索引第一次Search Path(沿着索引B+树的根节点一直遍历,到索引正确的叶节点位置)时使用,一次判断即可。
Index Last Key
  用来定位索引的终止范围,因此对于起始范围之后读到的每一条索引记录,均需要判断是否已经超过了Index Last Key的范围,若超过,则当前查询结束。
Index Filter
  用于过滤索引查询范围中不满足查询条件的记录,因此对于索引范围中的每一条记录,均需要与Index Filter进行对比,若不满足Index Filter则直接丢弃,继续读取索引下一条记录;
Table Filter
  用于过滤通过前面索引的记录,此时的记录已经满足了Index First Key与Index Last Key构成的范围,并且满足Index Filter的条件,回表读取了完整的记录,判断完整记录是否满足Table Filter中的查询条件,同样的,若不满足,跳过当前记录,继续读取索引的下一条记录,若满足,则返回记录,此记录满足了where的所有条件,可以返回给前端用户。
  ICP是MySQL 5.6版本中的新特性,是一种在存储引擎层使用索引过滤数据的一种优化方式。server层负责sql的解析,执行; 引擎层去真正的做数据/索引的读写。以前是server层命令引擎层按index key把相应的数据从数据表读出,传给server层,然后server层来按where条件(index filter和table filter)做选择。而在MySQL 5.6加入ICP后,Index Filter与Table Filter分离,将Index Filter下推到引擎层进行过滤,如果不符合条件则无须读数据表,减少了回表与返回Server层的记录交互开销,节省了disk IO,提高了SQL的执行效率。

1.2 MRR(Multi-Range Read)
  MRR的使用过程:先通过二级索引取出满足条件的二级索引和主键放到缓冲区(大小由参数read_rnd_buffer_size控制)中,当二级索引扫描到文件的末尾或者缓冲区已满,则使用快速排序对缓冲区中的内容按照主键进行排序(由于此时表是顺序的,就将随机IO变成了顺序IO,多页数据记录可一次性读入或根据此次的主键范围分次读入,以减少IO操作,提高查询效率)。

MRR的优点
1.MRR使数据行能够按照索引元组顺序而不是随机被访问
2.MRR允许批量处理需要通过索引元组访问数据行的操作请求(如:范围索引扫描和连接属性索引等连接)

二、Join算法

2.1 SNLJ&INLJ
Simple Nested-Loops Join

  SNLJ就是两层循环全量扫描连接的两张表,得到符合条件的两条记录则输出,这也就是让两张表做笛卡尔积。

Index Nested-Loops Join

  INLJ是在SNLJ的基础上做了优化,通过连接条件确定可用的索引,在Index Loop中扫描索引而不去扫描数据本身,从而提高Index Loop的效率。

  而INLJ也有缺点,就是如果扫描的索引是非聚簇索引,并且需要访问非索引的数据,会产生一个回表读取数据的操作,这就多了一次随机的I/O操作。

2.2 BNL&BKA
Block Nested-Loops Join

  将外层循环的行/结果集存入join buffer, 内层循环的每一行与整个buffer中的记录做比较,从而减少内层循环的次数。主要用于当被join的表上无索引。

Batched Key Access

  当被join的表能够使用索引时,就先好顺序,然后再去检索被join的表。对这些行按照索引字段进行排序,因此减少了随机IO。如果被Join的表上没有索引,则使用老版本的BNL策略。

2.3 HASH JOIN
  在8.0.18之前,MySQL只支持Nest Loop Join算法,最简单的就是Simple NestLoop Join,MySQL针对这个算法做了若干优化,实现了Block Nest Loop Join,Index NestLoop Join和Batched Key Access等,有了这些优化,在一定程度上能缓解对HashJoin的迫切程度。
  Hash Join是针对等值join场景的优化,基本思想是,将外表数据加载到内存,并建立hash表,这样只需要遍历一遍内表,就可以完成join操作,输出匹配的记录。如果数据能全部加载到内存当然好,逻辑也简单,一般称这种join为CHJ(Classic Hash Join),之前MariaDB就已经实现了这种HashJoin算法。如果数据不能全部load到内存,就需要分批load进内存,然后分批join。

In-Memory Join(CHJ)

  Hash Join一般包括两个过程,创建hash表的build过程和探测hash表的probe过程。

1).build phase

  遍历外表,以join条件为key,查询需要的列作为value创建hash表。这里涉及到一个选择外表的依据,主要是评估参与join的两个表(结果集)的大小来判断,谁小就选择谁,这样有限的内存更容易放下hash表。

2).probe phase

  hash表build完成后,然后逐行遍历内表,对于内表的每个记录,对join条件计算hash值,并在hash表中查找,如果匹配,则输出,否则跳过。所有内表记录遍历完,则整个过程就结束了。

MySQL 被攻击后的措施

  • 修改MySQL用户密码(密码最好包含:大小写字母、数字、特殊字符,长度长点)
  • 修改MySQL默认端口
  • 不对外连接(公司内部通过隧道连接)
  • 开启防火墙
systemctl status firewalld
开启防火墙:
systemctl start firewalld

关闭防火墙:
systemctl stop firewalld

指定端口服务进行开启防火墙。
--permanent永久生效,没有此参数重启后失效
firewall-cmd --zone=public --add-port=80/tcp --**permanent**
查看:
firewall-cmd --zone=public --query-port=80/tcp
删除:
firewall-cmd --zone=public --remove-port=80/tcp --permanent
重新载入:
firewall-cmd --reload

LINUX通过下面的命令可以开启允许对外访问的网络端口:
/sbin/iptables -I INPUT -p tcp --dport 8000 -j ACCEPT #开启8000端口
/etc/rc.d/init.d/iptables save #保存配置
/etc/rc.d/init.d/iptables restart #重启服务
/etc/init.d/iptables status # 查看端口是否已经开放

Referece

参考:

MySQL 各类场景的故障恢复概要(持续更新)

Have a good work&life! 2023/02 via LinHong


Similar Posts

Comments