百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

淘宝内部分享:怎么跳出MySQL的10个大坑(下)

moboyou 2025-03-06 13:07 39 浏览

TokuDB·特性分析· Optimize Table

本文说明一个物理升级导致的 "数据丢失"。

现象

在MySQL 5.1下新建key分表,可以正确查询数据。

drop table t1; create table t1 (c1 int , c2 int) PARTITION BY KEY (c2) partitions 5; insert into t1 values(1,1785089517),(2,); MySQL> select * from t1 where c2=1785089517; +------+------------+ | c1 | c2 | +------+------------+ | 1 | 1785089517 | +------+------------+ 1 row in set (0.00 sec) MySQL> select * from t1 where c2 is ; +------+------+ | c1 | c2 | +------+------+ | 2 | | +------+------+ 1 row in set (0.00 sec)

而直接用MySQL5.5或MySQL5.6启动上面的5.1实例,发现(1,1785089517)这行数据不能正确查询出来。

alter table t1 PARTITION BY KEY ALGORITHM = 1 (c2) partitions 5; MySQL> select * from t1 where c2 is ; +------+------+ | c1 | c2 | +------+------+ | 2 | | +------+------+ 1 row in set (0.00 sec) MySQL> select * from t1 where c2=1785089517; Empty set (0.00 sec)

原因分析

跟踪代码发现,5.1 与5.5,5.6 key hash算法是有区别的。

5.1 对于非空值的处理算法如下

void my_hash_sort_bin(const CHARSET_INFO *cs __attribute__((unused)), const uchar *key, size_t len,ulong *nr1, ulong *nr2) { const uchar *pos = key; key+= len; for (; pos < (uchar*) key ; pos++) { nr1[0]^=(ulong) ((((uint) nr1[0] & 63)+nr2[0]) * ((uint)*pos)) + (nr1[0] << 8); nr2[0]+=3; } }

通过此算法算出数据(1,1785089517)在第3个分区

5.5和5.6非空值的处理算法如下

void my_hash_sort_simple(const CHARSET_INFO *cs, const uchar *key, size_t len, ulong *nr1, ulong *nr2) { register uchar *sort_order=cs->sort_order; const uchar *end; /* Remove end space. We have to do this to be able to compare 'A ' and 'A' as identical */ end= skip_trailing_space(key, len); for (; key < (uchar*) end ; key++) { nr1[0]^=(ulong) ((((uint) nr1[0] & 63)+nr2[0]) * ((uint) sort_order[(uint) *key])) + (nr1[0] << 8); nr2[0]+=3; } }

通过此算法算出数据(1,1785089517)在第5个分区,因此,5.5,5.6查询不能查询出此行数据。

5.1,5.5,5.6对于空值的算法还是一致的,如下

if (field->is_) { nr1^= (nr1 << 1) | 1; continue; }

都能正确算出数据(2, )在第3个分区。因此,空值可以正确查询出来。

那么是什么导致非空值的hash算法走了不同路径呢?在5.1下,计算字段key hash固定字符集就是my_charset_bin,对应的hash 函数就是前面的my_hash_sort_simple。而在5.5,5.6下,计算字段key hash的字符集是随字段变化的,字段c2类型为int对应my_charset_numeric,与之对应的hash函数为my_hash_sort_simple。具体可以参考函数Field::hash

那么问题又来了,5.5后为什么算法会变化呢?原因在于官方关于字符集策略的调整,详见WL#2649 。

兼容处理

前面讲到,由于hash 算法变化,用5.5,5.6启动5.1的实例,导致不能正确查询数据。那么5.1升级5.5,5.6就必须兼容这个问题.MySQL 5.5.31以后,提供了专门的语法 ALTER TABLE ... PARTITION BY ALGORITHM=1 [LINEAR] KEY ... 用于兼容此问题。对于上面的例子,用5.5或5.6启动5.1的实例后执行

MySQL> alter table t1 PARTITION BY KEY ALGORITHM = 1 (c2) partitions 5; Query OK, 2 rows affected (0.02 sec) Records: 2 Duplicates: 0 Warnings: 0
MySQL> select * from t1 where c2=1785089517; +------+------------+ | c1 | c2 | +------+------------+ | 1 | 1785089517 | +------+------------+ 1 row in set (0.00 sec)

数据可以正确查询出来了。

而实际上5.5,5.6的MySQL_upgrade升级程序已经提供了兼容方法。MySQL_upgrade 执行check table xxx for upgrade 会检查key分区表是否用了老的算法。如果使用了老的算法,会返回

MySQL> CHECK TABLE t1 FOR UPGRADE\G *************************** 1. row *************************** Table: test.t1 Op: check Msg_type: error Msg_text: KEY  partitioning changed, please run: ALTER TABLE `test`.`t1` PARTITION BY KEY /*!50611 ALGORITHM = 1 */ (c2) PARTITIONS 5 *************************** 2. row *************************** Table: test.t1 Op: check Msg_type: status Msg_text: Operation failed 2 rows in set (0.00 sec)

检查到错误信息后会自动执行以下语句进行兼容。

ALTER TABLE `test`.`t1` PARTITION BY KEY /*!50611 ALGORITHM = 1 */ (c2) PARTITIONS 5。

MySQL · 捉虫动态· MySQL client crash一例

背景

客户使用MySQLdump导出一张表,然后使用MySQL -e 'source test.dmp'的过程中client进程crash,爆出内存的segment fault错误,导致无法导入数据。

问题定位

test.dmp文件大概50G左右,查看了一下文件的前几行内容,发现:

 A partial dump from a server that has GTIDs will by default include the GTIDs of all transactions, even those that changed suppressed parts of the database If you don't want to restore GTIDs pass set-gtid-purged=OFF. To make a complete dump, pass... -- MySQL dump 10.13 Distrib 5.6.16, for Linux (x86_64) -- -- Host: 127.0.0.1 Database: carpath -- ------------------------------------------------------ -- Server version 5.6.16-log /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;

问题定位到第一行出现了不正常warning的信息,是由于客户使用MySQLdump命令的时候,重定向了stderr。即:

    MySQLdump ...>/test.dmp 2>&1

导致error或者warning信息都重定向到了test.dmp, 最终导致失败。

问题引申

问题虽然定位到了,但却有几个问题没有弄清楚:

问题1. 不正常的sql,执行失败,报错出来就可以了,为什么会导致crash?

    MySQL.cc::add_line函数中,在读第一行的时候,读取到了don't,发现有一个单引号,所以程序死命的去找匹配的另外一个单引号,导致不断的读取文件,分配内存,直到crash。
    假设没有这个单引号,MySQL读到第六行,发现;号,就会执行sql,并正常的报错退出。

问题2. 那代码中对于大小的边界到底是多少?比如insert语句支持batch insert时,语句的长度多少,又比如遇到clob字段呢?

  • 首先clob字段的长度限制。clob家族类型的column长度受限于max_allowed_packet的大小,MySQL 5.5中,对于max_allowd_packet的大小限制在(1024, 1024*1024*1024)之间。

  • MySQLdump导出insert语句的时候,如何分割insert语句?MySQLdump时候支持insert t1 value,,;这样的batch insert语句。 MySQLdump其实是根据opt_net_buffer_length来进行分割,当一个insert语句超过这个大小,就强制分割到下一个insert语句中,这样更多的是在做网络层的优化。又如果遇到大的clob字段怎么办? 如果一行就超过了opt_net_buffer_length,那就强制每一行都分割。

  • MySQL client端读取dump文件的时候, 到底能分配多大的内存?MySQL.cc中定义了:#define MAX_BATCH_BUFFER_SIZE (1024L * 1024L * 1024L)。 也就是MySQL在执行语句的时候,最多只能分配1G大小的缓存。

所以,正常情况下,max_allowed_packet现在的最大字段长度和MAX_BATCH_BUFFER_SIZE限制的最大insert语句,是匹配的。

RDS问题修复原则

从问题的定位上来看,这一例crash属于客户错误使用MySQLdump导致的问题,Aliyun RDS分支对内存导致的crash问题,都会定位并反馈给用户。 但此例不做修复,而是引导用户正确的使用MySQLdump工具。

MySQL · 捉虫动态· 设置 gtid_purged 破坏AUTO_POSITION复制协议

bug描述

Oracle 最新发布的版本 5.6.22 中有这样一个关于GTID的bugfix,在主备场景下,如果我们在主库上 SET GLOBAL GTID_PURGED = "some_gtid_set",并且 some_gtid_set 中包含了备库还没复制的事务,这个时候如果备库接上主库的话,预期结果是主库返回错误,IO线程挂掉的,但是实际上,在这种场景下主库并不报错,只是默默的把自己 binlog 中包含的gtid事务发给备库。这个bug的造成的结果是看起来复制正常,没有错误,但实际上备库已经丢事务了,主备很可能就不一致了。

背景知识

  • binlog GTID事件


binlog 中记录的和GTID相关的事件主要有2种,Previous_gtids_log_event 和 Gtid_log_event,前者表示之前的binlog中包含的gtid的集合,后者就是一个gtid,对应一个事务。一个 binlog 文件中只有一个 Previous_gtids_log_event,放在开头,有多个 Gtid_log_event,如下面所示

Previous_gtids_log_event // 此 binlog 之前的所有binlog文件包含的gtid集合 Gtid_log_event // 单个gtid event Transaction Gtid_log_event Transaction . . . Gtid_log_event Transaction
  • 备库发送GTID集合给主库

我们知道备库的复制线程是分IO线程和SQL线程2种的,IO线程通过GTID协议或者文件位置协议拉取主库的binlog,然后记录在自己的relay log中;SQL线程通过执行realy log中的事件,把其中的操作都自己做一遍,记入本地binlog。在GTID协议下,备库向主库发送拉取请求的时候,会告知主库自己已经有的所有的GTID的集合,Retrieved_Gtid_Set + Executed_Gtid_Set,前者对应 realy log 中所有的gtid集合,表示已经拉取过的,后者对应binlog中记录有的,表示已经执行过的;主库在收到这2个总集合后,会扫描自己的binlog,找到合适的binlog然后开始发送。

  • 主库如何找到要发送给备库的第一个binlog

主库将备库发送过来的总合集记为 slave_gtid_executed,然后调用
find_first_log_not_in_gtid_set(slave_gtid_executed),这个函数的目的是从最新到最老扫描binlog文件,找到第一个含有不存在 slave_gtid_executed 这个集合的gtid的binlog。在这个扫描过程中并不需要从头到尾读binlog中所有的gtid,只需要读出 Previous_gtids_log_event ,如果Previous_gtids_log_event 不是 slave_gtid_executed的子集,就继续向前找binlog,直到找到为止。

这个查找过程总会停止的,停止条件如下:

  1. 找到了这样的binlog,其Previous_gtids_log_event 是slave_gtid_executed子集

  2. 在往前读binlog的时候,发现没有binlog文件了(如被purge了),但是还没找到满足条件的Previous_gtids_log_event,这个时候主库报错

  3. 一直往前找,发现Previous_gtids_log_event 是空集

在条件2下,报错信息是这样的

Got fatal error 1236 from master when reading data from binary log: 'The slave is connecting using CHANGE MASTER TO MASTER_AUTO_POSITION = 1, but the master has purged binary logs containing GTIDs that the slave requires.

其实上面的条件3是条件1的特殊情况,这个bugfix针对的场景就是条件3这种,但并不是所有的符合条件3的场景都会触发这个bug,下面就分析下什么情况下才会触发bug。

bug 分析

假设有这样的场景,我们要用已经有MySQL实例的备份重新做一对主备实例,不管是用 xtrabackup 这种物理备份工具或者MySQLdump这种逻辑备份工具,都会有2步操作,

  1. 导入数据

  2. SET GLOBAL GTID_PURGED ="xxxx"

步骤2是为了保证GTID的完备性,因为新实例已经导入了数据,就需要把生成这些数据的事务对应的GTID集合也设置进来。

正常的操作是主备都要做这2步的,如果我们只在主库上做了这2步,备库什么也不做,然后就直接用 GTID 协议把备库连上来,按照我们的预期这个时候是应该出错的,主备不一致,并且主库的binlog中没东西,应该报之前停止条件2报的错。但是令人大跌眼镜的是主库不报错,复制看起来是完全正常的。

为啥会这样呢,SET GLOBAL GTID_PURGED 操作会调用
MySQL_bin_log.rotate_and_purge切换到一个新的binlog,并把这个GTID_PURGED 集合记入新生成的binlog的Previous_gtids_log_event,假设原有的binlog为A,新生成的为B,主库刚启动,所以A就是主库的第一个binlog,它之前啥也没有,A的Previous_gtids_log_event就是空集,并且A中也不包含任何GTID事件,否则SET GLOBAL GTID_PURGED是做不了的。按照之前的扫描逻辑,扫到A是肯定会停下来的,并且不报错。

bug 修复

官方的修复就是在主库扫描查找binlog之前,判断一下 gtid_purged 集合不是不比slave_gtid_executed大,如果是就报错,错误信息和条件2一样 Got fatal error 1236 from master when reading data from binary log: 'The slave is connecting using CHANGE MASTER TO MASTER_AUTO_POSITION = 1, but the master has purged binary logs containing GTIDs that the slave requires。

MySQL · 捉虫动态· replicate filter 和 GTID 一起使用的问题

问题描述

当单个 MySQL 实例的数据增长到很多的时候,就会考虑通过库或者表级别的拆分,把当前实例的数据分散到多个实例上去,假设原实例为A,想把其中的5个库(db1/db2/db3/db4/db5)拆分到5个实例(B1/B2/B3/B4/B5)上去。

拆分过程一般会这样做,先把A的相应库的数据导出,然后导入到对应的B实例上,但是在这个导出导入过程中,A库的数据还是在持续更新的,所以还需在导入完后,在所有的B实例和A实例间建立复制关系,拉取缺失的数据,在业务不繁忙的时候将业务切换到各个B实例。

在复制搭建时,每个B实例只需要复制A实例上的一个库,所以只需要重放对应库的binlog即可,这个通过 replicate-do-db 来设置过滤条件。如果我们用备库上执行 show slave status\G 会看到Executed_Gtid_Set是断断续续的,间断非常多,导致这一列很长很长,看到的直接效果就是被刷屏了。

为啥会这样呢,因为设了replicate-do-db,就只会执行对应db对应的event,其它db的都不执行。主库的执行是不分db的,对各个db的操作互相间隔,记录在binlog中,所以备库做了过滤后,就出现这种断断的现象。

除了这个看着不舒服外,还会导致其它问题么?

假设我们拿B1实例的备份做了一个新实例,然后接到A上,如果主库A又定期purge了老的binlog,那么新实例的IO线程就会出错,因为需要的binlog在主库上找不到了;即使主库没有purge 老的binlog,新实例还要把主库的binlog都从头重新拉过来,然后执行的时候又都过滤掉,不如不拉取。

有没有好的办法解决这个问题呢?SQL线程在执行的时候,发现是该被过滤掉的event,在不执行的同时,记一个空事务就好了,把原事务对应的GTID位置占住,记入binlog,这样备库的Executed_Gtid_Set就是连续的了。

bug 修复

对这个问题,官方有一个相应的bugfix,参见 revno: 5860 ,有了这个patch后,备库B1的 SQL 线程在遇到和 db2-db5 相关的SQL语句时,在binlog中把对应的GTID记下,同时对应记一个空事务。

这个 patch 只是针对Query_log_event,即 statement 格式的 binlog event,那么row格式的呢? row格式原来就已经是这种行为,通过check_table_map 函数来过滤库或者表,然后生成一个空事务。

另外这个patch还专门处理了下 CREATE/DROP TEMPORARY TABLE 这2种语句,我们知道row格式下,对临时表的操作是不会记入binlog的。如果主库的binlog格式是 statement,备库用的是 row,CREATE/DROP TEMPORARY TABLE 对应的事务传到备库后,就会消失掉,Executed_Gtid_Set集合看起来是不连续的,但是主库的binlog记的gtid是连续的,这个 patch 让这种情况下的CREATE/DROP TEMPORARY TABLE在备库同样记为一个空事务。

TokuDB·特性分析· Optimize Table

来自一个TokuDB用户的“投诉”:

https://mariadb.atlassian.net/browse/MDEV-6207

现象大概是:

用户有一个MyISAM的表test_table:

 CREATE TABLE IF NOT EXISTS `test_table` ( `id` int(10) unsigned NOT , `pub_key` varchar(80) NOT , PRIMARY KEY (`id`), KEY `pub_key` (`pub_key`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1;

转成TokuDB引擎后表大小为92M左右:

 47M _tester_testdb_sql_61e7_1812_main_ad88a6b_1_19_B_0.tokudb 45M _tester_testdb_sql_61e7_1812_key_pub_key_ad88a6b_1_19_B_1.tokudb

执行"OPTIMIZE TABLE test_table":

 63M _tester_testdb_sql_61e7_1812_main_ad88a6b_1_19_B_0.tokudb 61M _tester_testdb_sql_61e7_1812_key_pub_key_ad88a6b_1_19_B_1.tokudb

再次执行"OPTIMIZE TABLE test_table":

 79M _tester_testdb_sql_61e7_1812_main_ad88a6b_1_19_B_0.tokudb 61M _tester_testdb_sql_61e7_1812_key_pub_key_ad88a6b_1_19_B_1.tokudb

继续执行:

基本稳定在这个大小。

主索引从47M-->63M-->79M,执行"OPTIMIZE TABLE"后为什么会越来越大?

这得从TokuDB的索引文件分配方式说起,当内存中的脏页需要写到磁盘时,TokuDB优先在文件末尾分配空间并写入,而不是“覆写”原块,原来的块暂时成了“碎片”。

这样问题就来了,索引文件岂不是越来越大?No, TokuDB会把这些“碎片”在checkpoint时加入到回收列表,以供后面的写操作使用,看似79M的文件其实还可以装不少数据呢!

嗯,这个现象解释通了,但还有2个问题:

  1. 在执行这个语句的时候,TokuDB到底在做什么呢? 在做toku_ft_flush_some_child,把内节点的缓冲区(message buffer)数据刷到最底层的叶节点。

  2. 在TokuDB里,OPTIMIZE TABLE有用吗? 作用非常小,不建议使用,TokuDB是一个"No Fragmentation"的引擎。

程序员日志

打造面向资深开发者的第一新媒体

深度丨有料丨有意思

【欢迎投稿】

程序员日志微信号:IT_Log

投稿地址:IT_Log@163.com

日志君QQ:167796765

相关推荐

iis部署php项目(iis发布php)

1.启动iis服务器最后点击确定就完成了2.打开iis点击进入即可3.创建网站进入添加网站。添加注意事项如图所示!启动、浏览、重启、停止网站这个如图所示。需要说明的是只要修改了配置就必须重启下网站4...

Win10安装Apache和PHP(apache安装php模块)

说明:虽然PHPStudy之类的软件给我们提供了方便的集成环境,但是其使用的是非线程安全的PHP版本(NotThreadSafe,NTS),某些功能不可以使用。所以,我们还需要自己再安装一个Apa...

两个php框架在一个网站上开发好吗

框架就是通过提供一个开发Web程序的基本架构,PHP开发框架把PHPWeb程序开发摆到了流水线上。换句话说,PHP开发框架有助于促进快速软件开发(RAD),这节约了你的时间,有助于创建更为稳定的程序,...

Ubuntu linux 上的 Nginx 和 Php 安装

教程-在UbuntuLinux上安装Nginx1.安装Nginx服务器和所需的包。apt-getupdatebrapt-getinstallnginx2.在Nginx配置文件...

网站开发初级(3) 之 PHP运行环境搭建

这篇文章主要是讲windows下php环境的搭建这里我们用的是phpstudy,作为入门学者足够了,解压安装就能用了,适当配置下基本能应付我们的开发要求下载地址:http://rj.baidu.co...

真实案例:优化用老PHP7.4的网站让其速度提高4倍

我让反应迟钝的PHP应用程序性能提升了4倍,而且我甚至没有改动任何源代码。没有重构,没有重写,只是进行了一系列精心规划的配置调整和部署优化。这篇文章将详细告诉你我是如何做到的。如果你的PHP应用程序运...

mac下快速搭建本地php开发环境(macbook开发php)

本教程主要是告诉大家,怎么快速的在mac下搭建php+mysql服务。一、安装CommandLineTools苹果系统下很多开发工具是基于CommandLineTools,所以安装它是首先。...

php手把手教你做网站(二十)vue+tp6简单案例(demo)

很多时候搭建好了环境,但是不知道怎么入手去开发。下面我们通过简单案例说明如何快速入门开发模块:例1:开发helloworld模块搭建好环境,新建项目以后,进入项目所在文件夹,依次进入src/compo...

Windows安装phpstudy(windows安装docker desktop)

说明:phpstudy是一个PHP+MySQL+Apache的集成环境,可以减少单独部署各个所需软件的麻烦,以及更加方便地切换版本。phpenv、wamp等软件的作用一样。由于环境的不同,安装过程中可...

服务器安装PHP网站的运行环境(php的服务器app)

首先要确认的是你的服务器的系统,Windows、或者linux系统。要想在Windows系统里运行php网站,可以选择iis或者Apache,如果你单独的去安装,可能会花很多时间去配置这个软件直接问题...

折腾群晖NAS:使用群晖web功能搭建个人博客「 小白玩NAS系列」

大家好,今天分享给大家如何利用群晖自带的web功能,来搭建一个属于自己的个人网站或者博客。搭建群晖web环境1、首先进入套件中心,下载webstation,群晖自带的web环境,安装会提示安装PHP...

黑客搭建钓鱼平台,手把手教你如何钓鱼?

跨站脚本攻击XSS:通过HTML注入篡改了网页,插入了恶意的脚本,从而用户浏览网页时,控制用户浏览器的一种攻击那么,我们搭建一个XSS钓鱼平台吧,注意:这个平台仅用于学习和测试,小伙伴们不要动有坏心思...

PHP 环境 搭建教程(php环境搭建apache)

PHP是一种编程语言,很多网站都用PHP语言编写,我们有时候需要测试一个网站,就需要PHP环境才能运行,又要安装Apache、又要安装MySQL……真的非常麻烦。其实我们可以使用PHP集成...

php手把手教你做网站(六)IIS创建站点注意事项

站点名称:随便填写;应用程序池:创建站点的时候会自动生成对应的,默认就好;物理路径:选择站点所在目录;类型:本地一定是http,网上可能会有https;IP地址:默认端口默认,如果是别的端口,网址访问...

分享PHP网站开发过程中的一些经验

现在的网站建设语言有几种,分别是ASP.NET,PHP,JSP这三种最为常见,这三种语言做出来的网站各有各的特点,asp属于最成熟的建站模式,jsp一般使用在大型网站系统上,对于php是目前比较流行的...