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

MySQL 8.0 Study 003 Tips

2023-01-27

MySQL 8.0 Study 003 Tips

学习系列

  • 2022 年数据库发展总结
  • 组提交
  • innodb_log_buffer_size
  • mysql表空间回收
  • MySQL GTID是什么
  • secure-file-priv特性

2022 年数据库发展总结

转载:

2022 年数据库发展总结

Q1. 中国和海外数据库的差距还有多远?

Q2. 未来是 OLTP 还是 OLAP ?

Q3. 从国际上来看 HTAP 是不是未来?

Q4. MySQL DBA 和大数据从业者会有什么改变?

Q5. 现在还是不是数据方向的创业好的时机?

01. 组提交

图解MySQL 原理解析 MySQL组提交(group commit)

WAL机制 (Write Ahead Log)定义:

WAL指的是对数据文件进行修改前,必须将修改先记录日志。MySQL为了保证ACID中的一致性和持久性,使用了WAL。

Redo log的作用:

Redo log就是一种WAL的应用。当数据库忽然掉电,再重新启动时,MySQL可以通过Redo log还原数据。也就是说,每次事务提交时,不用同步刷新磁盘数据文件,只需要同步刷新Redo log就足够了。相比写数据文件时的随机IO,写Redo log时的顺序IO能够提高事务提交速度。

组提交的作用:

在没有开启binlog时

Redo log的刷盘操作将会是最终影响MySQL TPS的瓶颈所在。为了缓解这一问题,MySQL使用了组提交,将多个刷盘操作合并成一个,如果说10个事务依次排队刷盘的时间成本是10,那么将这10个事务一次性一起刷盘的时间成本则近似于1。

当开启binlog时

为了保证Redo log和binlog的数据一致性,MySQL使用了二阶段提交,由binlog作为事务的协调者。而 引入二阶段提交 使得binlog又成为了性能瓶颈,先前的Redo log 组提交 也成了摆设。为了再次缓解这一问题,MySQL增加了binlog的组提交,目的同样是将binlog的多个刷盘操作合并成一个,结合Redo log本身已经实现的 组提交,分为三个阶段(Flush 阶段、Sync 阶段、Commit 阶段)完成binlog 组提交,最大化每次刷盘的收益,弱化磁盘瓶颈,提高性能。

02. innodb_log_buffer_size

MySQL 官方文档说 innodb_log_buffer_size 是 redo log 的写缓存,设置大一点能减少写操作。

innodb_log_buffer_size 是 redo log 的写缓存,如果 buffer 不够大,就会发生多次 IO write,将缓存中的数据刷到磁盘;
innodb_log_buffer_size 越大,IO 次数越少。

一般存储上,虽然 IO write 次数少了,但 write 的数据量是差不多的,所以整体的 IO 延迟总体差不多;

在好点的存储上,如果数据量差不多, write 次数越少,延迟会越低。

这也就是为什么大家在调整这个参数后, 经常会感知不到性能的提升。

03. mysql表空间回收

delete导致的表空洞

delete 命令其实只是把记录的位置,或者数据页标记为了“可复用”,但磁盘文件的大小是不会变的。也就是说,通过 delete 命令是不能回收表空间的。这些可以复用,而没有被使用的空间,看起来就像是“空洞”。

insert导致的表空洞

如果数据是按照索引递增顺序插入的,那么索引是紧凑的。但如果数据是随机插入的,就可能造成索引的数据页分裂。

怎么清理空洞

  • 重建表
试想一下,如果你现在有一个表 A,需要做空间收缩,为了把表中存在的空洞去掉,你可以怎么做呢?你可以新建一个与表 A 结构相同的表 B,然后按照主键ID 递增的顺序,把数据一行一行地从表 A 里读出来再插入到表 B 中。由于表 B 是新建的表,所以表 A 主键索引上的空洞,在表 B 中就都不存在了。显然地,表 B 的主键索引更紧凑,数据页的利用率也更高。如果我们把表 B 作为临时表,数据从表 A 导入表 B 的操作完成后,用表 B 替换 A,从效果上看,就起到了收缩表 A 空间的作用。
  • alter table 方式
alter table A engine=InnoDB 命令来重建表。在 MySQL 5.5 版本之前,这个命令的执行流程跟我们前面描述的差不多,区别只是这个临时表 B 不需要你自己创建,MySQL 会自动完成转存数据、交换表名、删除旧表的操作。

注:但是以上重建表的方式,在整个 DDL 过程中,表 A 中不能有更新。也就是说,这个 DDL 不是 Online 的。

  • Online DDL诞生
从5.6开始引入了online DDL,也就是说alter table A engine=InnoDB的时候,允许对表 A 做增删改操作。但是DDL会有短暂的MDL写锁,这个会很快转换为MDL读锁,因此,认为对于一个大表来说,Online DDL 最耗时的过程就是拷贝数据到临时表的过程,这个步骤的执行期间可以接受增删改操作。所以,相对于整个 DDL 过程来说,锁的时间非常短。对业务来说,就可以认为是 Online 的。

注意:对于很大的表来说,这个操作是很消耗 IO 和 CPU 资源的。因此,如果是线上服务,你要很小心地控制操作时间。如果想要比较安全的操作的话,我推荐你使用 GitHub 开源的 gh-ost 来做。

MySQL 最佳实践:gh-ost 工具使用详解

解决方案

从 MySQL 的同步原理来看,主要是 DDL 这个单独的操作会花费太久的时间,导致从库也会被卡主。那么解决这个问题的办法就很容易想到:“拆解” DDL 的操作,把一个大操作(大事务同理)拆分成多个小操作,减少单次操作的时间。

“拆解” DDL 操作一般会用到 MySQL Online DDL 的工具,比如 pt-osc,facebook-osc,oak-online-alter-table,gh-ost 等。这些工具的思路都比较类似,创建一个源表的镜像表,先执行完表结构变更,再把源表的全量数据和增量数据都同步过去,因此可以减少单个 DDL 操作引发的同步延迟,但是不能完全避免,因为大批量 insert 数据本身也是可能会产生一些延迟的,只是这部分延迟的时间会比大多数 DDL 要小很多。

MySQL 轻量级Online DDL工具 gh-ost

MySQL 8.0 Online DDL和pt-osc、gh-ost深度对比分析

MySQL GTID

GTID作用 主从环境中主库的dump线程可以直接通过GTID定位到需要发送的binary log的位置,而不需要指定binary log的文件名和位置,因而切换极为方便。

GTID实际上是由UUID+TID (即transactionId)组成的。其中UUID(即server_uuid) 产生于auto.conf文件(cat /data/mysql/data/auto.cnf),是一个MySQL实例的唯一标识。TID代表了该实例上已经提交的事务数量,并且随着事务提交单调递增,所以GTID能够保证每个MySQL实例事务的执行(不会重复执行同一个事务,并且会补全没有执行的事务)。GTID在一组复制中,全局唯一。 

对于2台主以上的结构优势异常明显,可以在数据不丢失的情况下切换新主。

通过GTID复制,这些在主从成立之前的操作也会被复制到从服务器上,引起复制失败。也就是说通过GTID复制都是从最先开始的事务日志开始,即使这些操作在复制之前执行。比如在server1上执行一些drop、delete的清理操作,接着在server2上执行change的操作,会使得server2也进行server1的清理操作。

直接使用CHANGE MASTER TO MASTER_HOST=’xxx’, MASTER_AUTO_POSITION命令就可以直接完成failover的工作。

开启GTID

MySQL 5.6 版本,在my.cnf文件中添加:

gtid_mode=on (必选)                    #开启gtid功能
enforce-gtid-consistency=1 (必选)      #也可以将1写为on
log_bin=log-bin=mysql-bin (必选)       #开启binlog二进制日志功能
log-slave-updates=1 (必选)             #也可以将1写为on

MySQL 5.7或更高版本,在my.cnf文件中添加:

gtid_mode=on    (必选)
enforce-gtid-consistency=1  (必选)
log_bin=mysql-bin           (可选)    #高可用切换,最好开启该功能
log-slave-updates=1     (可选)       #高可用切换,最好打开该功能

GTID的缺点

-  不支持非事务引擎;
-  不支持create table ... select 语句复制(主库直接报错);(原理: 会生成两个sql, 一个是DDL创建表SQL, 一个是insert into 插入数据的sql; 由于DDL会导致自动提交, 所以这个sql至少需要两个GTID, 但是GTID模式下, 只能给这个sql生成一个GTID)
-  不允许一个SQL同时更新一个事务引擎表和非事务引擎表;
-  在一个复制组中,必须要求统一开启GTID或者是关闭GTID;
-  开启GTID需要重启 (mysql5.7除外);
-  开启GTID后,就不再使用原来的传统复制方式;
-  对于create temporary table 和 drop temporary table语句不支持;
-  不支持sql_slave_skip_counter;

GTID跳过事务的方法 开启GTID以后,无法使用sql_slave_skip_counter跳过事务,因为主库会把从库缺失的GTID,发送给从库,所以skip是没有用的。

为了提前发现问题,在gtid模式下,直接禁止使用set global sql_slave_skip_counter =x。

正确的做法: 通过set gtid_next= ‘aaaa’(’aaaa’为待跳过的事务),然后执行BIGIN; 接着COMMIT产生一个空事务,占据这个GTID,再START SLAVE,会发现下一条事务的GTID已经执行过,就会跳过这个事务了。如果一个GTID已经执行过,再遇到重复的GTID,从库会直接跳过,可看作GTID执行的幂等性。

因为是通过GTID来进行复制的,也需要跳过这个事务从而继续复制,这个事务可以到主上的binlog里面查看:因为不知道找哪个GTID上出错,所以也不知道如何跳过哪个GTID

1、show slave status里的信息里可以找到在执行Master里的POS:151
2、通过mysqlbinlog找到了GTID:
3、stop slave;
4、set session gtid_next='4e659069-3cd8-11e5-9a49-001c4270714e:1'
5、begin; commit;
6、SET SESSION GTID_NEXT = AUTOMATIC;   #把gtid_next设置回来
7、start slave;  #开启复制

1)对于跳过一个错误,找到无法执行事务的编号,比如是2a09ee6e-645d-11e7-a96c-000c2953a1cb:1-10
mysql> stop slave;
mysql> set gtid_next='2a09ee6e-645d-11e7-a96c-000c2953a1cb:1-10';
mysql> begin;
mysql> commit;
mysql> set gtid_next='AUTOMATIC';
mysql> start slave;
 
2)上面方法只能跳过一个事务,那么对于一批如何跳过?
在主库执行"show master status",看主库执行到了哪里,比如:2a09ee6e-645d-11e7-a96c-000c2953a1cb:1-33,那么操作如下:
mysql> stop slave;
mysql> reset master;
mysql> set global gtid_purged='2a09ee6e-645d-11e7-a96c-000c2953a1cb:1-33';
mysql> start slave;

如何升级成 GTID replication 

先介绍几个重要GTID_MODE的参数:
GTID_MODE = OFF
不产生Normal_GTID,只接受来自master的ANONYMOUS_GTID
 
GTID_MODE = OFF_PERMISSIVE
不产生Normal_GTID,可以接受来自master的ANONYMOUS_GTID & Normal_GTID
 
GTID_MODE = ON_PERMISSIVE
产生Normal_GTID,可以接受来自master的ANONYMOUS_GTID & Normal_GTID
 
GTID_MODE = ON
产生Normal_GTID,只接受来自master的Normal_GTID
 
归纳总结:
1)当master产生Normal_GTID的时候,如果slave的gtid_mode(OFF)不能接受Normal_GTID,那么就会报错
2)当master产生ANONYMOUS_GTID的时候,如果slave的gtid_mode(ON)不能接受ANONYMOUS_GTID,那么就会报错
3)设置auto_position的条件: 当master的gtid_mode=ON时,slave可以为OFF_PERMISSIVE,ON_PERMISSIVE,ON。
   除此之外,都不能设置auto_position = on
 
============================================
下面开始说下如何online 升级为GTID模式?
 
step 1: 每台server执行
检查错误日志,直到没有错误出现,才能进行下一步
mysql> SET @@GLOBAL.ENFORCE_GTID_CONSISTENCY = WARN;
 
step 2: 每台server执行
mysql> SET @@GLOBAL.ENFORCE_GTID_CONSISTENCY = ON;
 
step 3: 每台server执行
不用关心一组复制集群的server的执行顺序,只需要保证每个Server都执行了,才能进行下一步
mysql> SET @@GLOBAL.GTID_MODE = OFF_PERMISSIVE;
 
step 4: 每台server执行
不用关心一组复制集群的server的执行顺序,只需要保证每个Server都执行了,才能进行下一步
mysql> SET @@GLOBAL.GTID_MODE = ON_PERMISSIVE;
 
step 5: 在每台server上执行,如果ONGOING_ANONYMOUS_TRANSACTION_COUNT=0就可以
不需要一直为0,只要出现过0一次,就ok
mysql> SHOW STATUS LIKE 'ONGOING_ANONYMOUS_TRANSACTION_COUNT';
 
step 6: 确保所有anonymous事务传递到slave上了
#master上执行
mysql> SHOW MASTER STATUS;
 
#每个slave上执行
mysql> SELECT MASTER_POS_WAIT(file, position);
 
或者,等一段时间,只要不是大的延迟,一般都没问题
 
step 7: 每台Server上执行
mysql> SET @@GLOBAL.GTID_MODE = ON;
 
step 8: 在每台server上将my.cnf中添加好gtid配置
gtid_mode=on
enforce-gtid-consistency=1
log_bin=mysql-bin
log-slave-updates=1
 
step 9: 在从机上通过change master语句进行复制
mysql> STOP SLAVE;
mysql> CHANGE MASTER TO MASTER_AUTO_POSITION = 1;
mysql> START SLAVE;

MySQL–GTID是什么

MySQL GTID 日常维护案例两则

MySQL limit优化

在MySQL中Limit有两种语法:

limit offset, rows
limit rows

其中offset表示偏移量,rows表示要返回的记录条数。比如我们要查出第1000到第1010行的记录,就会写成 limit 999, 10 。 一般我们在实际开发中做分页查询的时候,rows不会太大,因为rows表示每页要显示的数量(你总不可能每页要显示个几万条吧)。 而随着页数的加深,offset就会变大,查询效率随之就变慢了。 为什么当offset很大时,查询效率会变慢呢? 比如当我们用 limit 1000000, 10 的时候,MySQL会先扫描满足条件的1000010行,扔掉前面的1000000行,返回后面的10行。所以offset越大的时候,扫描的行就越多,效率也就越慢了。

Limit Offset下推

https://help.aliyun.com/document_detail/437535.html?spm=a2c4g.11186623.0.0.cebf49b1u8jzR7

secure-file-priv特性

secure-file-priv参数是用来限制LOAD DATA, SELECT … OUTFILE, and LOAD_FILE()传到哪个指定目录的。

secure_file_priv的值为null ,表示限制mysqld 不允许导入|导出
当secure_file_priv的值为/tmp/ ,表示限制mysqld 的导入|导出只能发生在/tmp/目录下
当secure_file_priv的值没有具体值时,表示不对mysqld 的导入|导出做限制

如何查看secure-file-priv参数的值:

show global variables like '%secure%';

MYSQL新特性secure_file_priv对读写文件的影响: 此开关默认为NULL,即不允许导入导出。

解决问题: windows下:

修改my.ini 在[mysqld]内加入
secure_file_priv=

linux下:

修改my.cnf 在[mysqld]内加入
secure_file_priv=
MYSQL新特性secure_file_priv对读写文件的影响
然后重启mysql,再查询secure_file_priv

类似错误:

mysql [localhost:8032] {msandbox} (test) > select concat('insert into t1(id,c1,c2) values(',id,',','\'',c1,'\'',',','\'',c2,'\'',')') into outfile '/tmp/t1.txt' from t1;
ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement
mysql [localhost:8032] {msandbox} (test) > show variables like 'secure_file%';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| secure_file_priv | NULL  |
+------------------+-------+
1 row in set (0.00 sec)

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

my.cnf配置之后重启

mysql [localhost:8032] {msandbox} (test) > show variables like 'secure%';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| secure_file_priv |       |
+------------------+-------+
1 row in set (0.00 sec)

mysql [localhost:8032] {msandbox} (test) > select concat('insert into t1(id,c1,c2) values(',id,',','\'',c1,'\'',',','\'',c2,'\'',')') into outfile '/tmp/t1.txt' from t1;
Query OK, 2 rows affected (0.00 sec)

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

Referece

参考:

Sysbench的安装(Build Install)

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


Similar Posts

Comments