建议收藏|MySQL DBA 防坑指南

Posted 老叶茶馆_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了建议收藏|MySQL DBA 防坑指南相关的知识,希望对你有一定的参考价值。

导读

本文由茹憶老师根据5月2日晚上的直播内容整理而成,我直接转载,以下是正文内容。


2022年5月2日,在上海面临抗疫关键时刻的五一黄金假期,特别邀请了Oracle mysql ACE Director 叶金荣(叶老师),联合为大家带来了MySQL运维防坑的视频号在线直播,了解了这些坑可以让大家在日常运维更加平稳,全场300多人参与,直播持续了一个半小时,约1小时的分享和半小时的互动答疑,可谓干货满满。直播无回放,我们将直播涉及的关键内容进行了整理,形成了这份MySQL DBA防坑指南,建议大家可以收藏下来,类似问题参考解决(文末有下一场直播,欢迎预约)。

1. MySQL连接数问题

  • MySQL里的max_connections参数代表mysql数据库的最大连接数,参数默认是151,显然不适用于生产,如果请求大于默认连接数,就会出现无法连接数据库的错误,会遇到too many connections的报错信息。

  • Mysql5.5、mysql5.6、mysql5.7:默认的最大连接数都是151,上限为:100000

  • max_connections需要设置的一个合理的值,并需要做好监控,避免连接数满引发业务故障(生产环境建议5000-10000左右,没有固定的值,根据各自业务情况来定)。

  • 另外如果数据库连接数过高,需要分析业务端是否存在大量刷MySQL连接的情况,比如大量的短连接,连接没有正常关闭,代码逻辑问题等。

2. MySQL文件句柄设置

  • 在文件I/O中,要从一个文件读取数据,应用程序首先要调用操作系统函数并传送文件名,并选一个到该文件的路径来打开文件。该函数取回一个顺序号,即文件句柄(file handle),该文件句柄对于打开的文件是唯一的识别依据。要从文件中读取一块数据,应用程序需要调用函数ReadFile,并将文件句柄在内存中的地址和要拷贝的字节数传送给操作系统。当完成任务后,再通过调用系统函数来关闭该文件。

  • 在使用MySQL的过程中,有些用户碰到了打开文件句柄数过多的错误,查看用户实例的打开句柄个数,确实超过了系统设置的值,一旦出现了这种错误,将会带来连锁的各种错误(取决于当时正在操作什么类型的文件,以及什么操作)。

  • open_file_limits的设置的值,mysqld会通过setrlimit系统调用来初始化本进程可以使用的最大文件句柄数。

  • 操作系统范围限制,可以通过编辑/etc/sysctl.conf或编辑指令fs.file-max来增加Linux中打开文件的限制。

3. 注意SQL隐式转换的坑

在开发规范中,我们往往会要求研发避免在where条件中出现隐式类型转换,什么是隐式转换:即在where语句中条件的值和条件对应的列的数据类型不一致。如 where id=‘123’,而id的类型为bigint,或者where code=100,而code的类型为varchar,隐式转换会产生以下两个问题:

1. 隐式类型转换可能导致索引失效。

2. 隐式类型转换可能产生非预期的结果。

4. SQL为什么一会可以走到索引,一会走不到索引

有些时候开发同学会找到DBA,反馈有一条SQL有索引,之前也能走到索引,查询性能非常的高,突然收到慢查询报警,查了十多秒,这种情况产生的原因一般和数据内容有关。

MySQL如果通过索引选择扫描行数过多(大约20%以上,这个不是确定的),优化器会认为使用全表扫描更佳,从而会走全表扫描,当然很多时候其实还是走索引性能会更好一点,MySQL选择优化器并不是那么智能。

举个例子,微博的用户有一张评论表,查询我们这些小众用户的评论会走索引,查询非常的快,如果正好查询的是一个大V账号,有可能涉及数百万条数据导致没有走到索引。

5. 自增键重启后回溯问题

InnoDB 引擎的自增值,其实是保存在了内存里,并且到了 MySQL 8.0 版本后,才有了“自增值持久化”的能力,也就是才实现了“如果发生重启,表的自增值可以恢复为 MySQL 重启前的值”。

在 MySQL 5.7 及之前的版本,自增值保存在内存里,并没有持久化。每次重启后,第一次打开表的时候,都会去找自增值的最大值 max(id),然后将 max(id)+1 作为这个表当前的自增值。

举例来说,如果一个表当前数据行里最大的 id 是 10,AUTO_INCREMENT=11。这时候,我们删除 id=10 的行,AUTO_INCREMENT 还是 11。但如果马上重启实例,重启后这个表的 AUTO_INCREMENT 就会变成 10。也就是说,MySQL 重启可能会修改一个表的 AUTO_INCREMENT 的值。

6. 自增键用完怎么办

如果你用过或了解过MySQL,那你一定知道自增主键了。每个自增id都是定义了初始值,然后按照指定步长增长(默认步长是1)。

虽然,自然数是没有上限的,但是我们在设计表结构的时候,通常都会指定字段长度,那么,这时候id就有上限了。

在插入数据时有可能唯一主键冲、sql事务回滚、批量插入的时候,批量申请自增值等原因导致自增id是不连续的。

我们一般会将自增键的类型设置为int,数据范围为负21亿到正21亿,对于一个频繁插入删除数据的表来说,21亿是可能会被用完的,可能引发业务无法正常写入。

因此在建表的时候你需要考察你的表是否有可能达到这个上限,如果有可能,就应该创建成 8 个字节的 bigint unsigned。

另外可以将表的自增键做好监控,比如到达使用率的80%,就可以报警出来。

7. 大表删除hang的问题

MySQL里面直接对大表执行drop table删除有可能导致MySQL Hang住,对业务造成影响。删除超大表的前提是该表是独立表空间,然后按照如下步骤删除才能避免引起业务故障。

一、表创建一个硬链接

# du -sh pay_bills.ibd
175G       pay_bills.ibd
# 创建硬链接
# ln pay_bills.ibd pay_bills.ibd_hdlk

二、执行表删除

在Linux中,每个存储文件都会有指向该文件的Inode Index,多个文件名可以通过相同Inode Index指向相同一个存储文件。

如果该文件名引用的Inode Index上还被其他文件名引用,则只会删除该文件名和Inode Index之间的引用

如果该文件名引用的Inode Index上没有被其他文件名引用,则删除该文件名和Inode Index之间的引用并删除Inode Index指向的存储文件。

实际上只是删除了对 pay_bills.ibd 的一个文件引用,我们 pay_bills.ibd_hdlk 对物理文件的引用还是存在的,就不会执行OS级别的删除操作,IO波动不大,降低对MySQL的影响。

mysql> drop table pay_bills;
Query OK, 0 rows affected (3.24 sec)

三、执行文件删除

安装 truncate 工具

# yum install coreutils -y

执行删除脚本

#!/bin/bash
TRUNCATE=/usr/bin/truncate
for i in `seq 175 -2 1`; do
$TRUNCATE -s $iG pay_bills.ibd_hdlk
 sleep 1
done

最后删除硬连接 

rm -f pay_bills.ibd_hdlk

8. Adaptive Hash Index引发的问题

哈希(hash)是一种非常快的查找方法,在一般情况下这种查找的时间复杂度为O(1),即一般仅需要一次查找就能定位数据。而B+树的查找次数,取决于B+树的高度,在生产环境中,B+树的高度一般为3~4层,故需要3~4次的查询。

InnodB存储引擎会监控对表上各索引页的查询。如果观察到建立哈希索引可以带来速度提升,则建立哈希索引,称之为自适应哈希索引(Adaptive Hash Index,AHI) AHI是通过缓冲池的B+树页构造而来,因此建立的速度很快,而且不需要对整张表构建哈希索引。InnoDB存储引擎会自动根据访问的频率和模式来自动地为某些热点页建立哈希索引。

但是在DROP TABLE过程中,发现存在大量的线程处于OPENING TABLES的状态,DROP 时间非常的长。原因是当DROP TABLE时,InnoDB引擎还会删除表对应0的AHI(自适应哈希索引)。而这个过程需要持有一把数据字典的互斥锁、读写锁。

对于这个问题,可以在DROP TABLE的时候关闭AHI功能,甚至可以永久关闭AHI功能。

9. MHA切换VIP的问题

MHA是业界经典而且很多公司在用的HA切换工具,但是默认开源的MHA基于VIP漂移来进行切换,通过VIP漂移可能存在因为网卡问题漂移失败的问题,从而导致切换失败。

目前很多运维能力比较强的公司通过自建DNS,然后将MHA的VIP漂移的代码二次开发改造为切换DNS,业务连接DNS 域名来解决这个问题。

10. pt-archiver迁移为什么少了一条数据

pt-archiver属于大名鼎鼎的percona工具集的一员,是归档和清理MySQL大表数据的最佳轻量级工具之一。但是使用过程中发现迁移的数据少了一条(最后一条数据),原因是和工具的safe-auto-increment参数有关。

--[no]safe-auto-increment默认值:yes,指定不使用自增列(AUTO_INCREMENT)最大值对应的行进行归档。该选项在进行归档清除时会额外添加一条WHERE子句以防止工具删除单列升序字段具有的具有AUTO_INCREMENT属性最大值的数据行,为了在数据库重启之后还能使用到AUTO_INCREMENT对应的值,但这会引起无法归档或清除字段对应最大值的行。

11. pt-osc和ghost变更丢数据的问题

我们在使用pt-osc和gh-ost的过程中,发现了变更完丢数据的情况,知道了这个坑,就减少了删库跑路的风险。

pt-osc和gh-ost执行方式关于数据处理的区别,我们做了一个测试

测试表如下:

CREATE TABLE`ddltest` (
`id` int(11)NOT NULL AUTO_INCREMENT,
`name`varchar(10) DEFAULT NULL,
PRIMARY KEY(`id`)
)ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8




mysql>select * from ddltest;
+—-+——+
| id | name|
+—-+——+
|  1| a    |
|  2| b    |
+—-+——+

pt-osc和gh-ost执行方式关于数据处理的区别,大概总结一下:

  • 加字段设置not null没有default值:pt-osc执行报错(捕捉了warning,返回报错),gh-ost执行成功(因为会自动填充默认值),都不会丢数据。

  • 加唯一索引:pt-osc和gh-ost都会执行成功,但是都会丢失重复值之后的所有数据。

  • 加字段设置not null没有default值,同时加唯一索引:pt-osc执行失败不会丢数据,gh-ost执行成功会丢数据,表里只剩1条数据。

  • 加字段设置not null并设置default值,同时加唯一索引:pt-osc和gh-ost都可以执行成功,都会丢数据,表里只剩1条数据。

  • 加字段设置允许null值,加唯一索引不会丢数据,因为null不代表任何值,代表不重复的值,重复null值是可以加上唯一索引的。

最终最终总结:

  • 使用pt-osc和gh-ost加唯一索引很危险,一定要确保加唯一索引的字段没有重复值,不然别执行,否则终有一天你就是删库跑路大神。

  • 新增字段设置该字段为not null,同时该字段上加唯一索引,那也就等着死翘翘吧(not null不设置default值,使用pt-osc会捕捉到warning报错能免死一次,使用gh-ost的话直接丢数据,如果设置了default值,那不管是pt-osc还是gh-ost都会丢数据,直接死翘翘)。

针对此问题可以改进的地方:

  1. 完善SQL军规,新增字段时如果设置not null属性则必须带上default值。如果新增字段设置not null,禁止加唯一索引。

  2. 完善SQL审核流程,如果系统发现开发同学提交了新增唯一索引的SQL,则不允许开发自动执行,流转到DBA处理。

  3. 提高自我审核从严意识,同时在自动执行平台DBA审核界面,针对添加唯一索引,做强提醒功能。

  4. 针对加唯一索引的SQL,最好单独提交处理,走online ddl模式执行,尽量不用pt-osc和gh-ost。

如果使用新版OSC:Percona toolkil 3.0.13 ,参数–check-unique-key-change –check-alter 可以事先检查是否增加uniquekey,并报错。

‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

12. 数据库拆分引发的删库事件

讲一个真实案例,曾经,有一名前同事在做一次数据库拆分,拆分是通过搭建同步备库和切换进行的(备库只同步了需要拆分的数据库),操作完成后,原来服务器实例已经拆走的数据库需要删除,删除后发现新的数据库也没有了,一次删除事件由此产生。

产生的原因是由于新库拆分完成后,忘记断开和老库和同步,以致于在老库删除后新库也跟着没有了。

这个完全是操作和流程问题,也是希望大家能够引起重要,运维操作建议写好操作步骤,并且关键步骤(类似删库和删表)最好有double check,可以避免类似故障发生。

13.HA没有切换/监控没有正常报警

大家有没有遇到过这种情况,数据库故障了,却没有按预期的进行故障切换,监控报警也经常失效,我想说的是我们使用了大量的工具来让我们的运维更加稳定,但是本身工具是不是也是单点?是不是也要做好监控呢?

比如HA平台、备份平台、监控平台,这些平台的自身服务或者Agent进行我们也要做好对应的监控,预防上述问题发生。

14. df看空间越来越少,du却没有发现大文件

有时候在Linux主机用df -hT 查看磁盘占用已经满了,但是用du -sh * 又找不到是哪些文件目录占满了磁盘。

原因是当我们使用rm在linux上删除了大文件,但是如果有进程打开了这个大文件,却没有关闭这个文件的句柄,那么linux内核还是不会释放这个文件的磁盘空间

解决方法:

lsof -n | grep deleted  查看到占用的进程

然后用kill -9 进程号杀死进程就可以释放对应占用的空间了

批量处理命令:

lsof |awk '/deleted/print $2'|xargs kill -9

另外也看下有没有vim进程,如果使用vim打开了一个大文件,也会引起该问题。

15. 死锁要紧么,需要注意什么

如果是偶尔低频的死锁,没什么太大影响,如果是出现频率比较高,对业务影响比较大。分析死锁的原因最好能够复现死锁。

分析死锁可以使用show engine innodb status 命令得到锁的信息结合业务场景和业务代码进行分析。

16. text等大对象类型有什么风险

  1. text/blob大字段会引发页的分裂,影响性能,具体可以参考 浅析InnoDB Record Header及page overflow 

  2. text字段会引起表占用更多的物理磁盘空间,不合理的text浪费大量磁盘空间

17. CPU %user 为什么特别的高

一般MySQL服务器的CPU %user如果比较高,一般95%以上都是索引使用不当引起的,我们需要重点关注慢SQL,其中重点关注执行状态为send data/ creating sort index/ copying to tmp table / creating tmp table 的SQL,并跟踪优化。

18. 查询被hang住了,什么原因

如果查询hang住了,一般可以从以下几个方面进行排查:

  • 系统硬件问题

  • 系统负载太高

  • MySQL连接/线程异常

  • InnoDB因素

可能的原因有:

  • 系统负载高(CPU、IOWAIT、SWAP、中断)等。

  • DNS反解析问题

  • 垃圾SQL太多

  • InnoDB线程排队

  • 磁盘满了

  • 有锁等待

  • redo、binlog 2PC、semi-sync delay、binlog group commit delay

  • purge lag & checkpoint lag

19. mysql crash了,怎么办

mysql crash,可以从以下几个方面去排查分析:

  • 分析error log, 判断是否因为触发bug而crash了

  • 分析error log,判断是否因为innodb Semaphore wait太久而crash

  • 判断是否因为数据页损坏而crash

那么如果mysql crash了该怎么办呢?

  • 通过google及官方站搜索bug库查找解决方案

  • 分析日志,尽力还原现场,找出根因

  • 修复数据页,导出数据重建

  • 修复硬件故障和系统故障

欢迎加入微信群一起交流探讨(加作者微信备注加入技术交流群):

五一劳动节的最后一场直播

本次为大家带来MySQL从5.6升级到8.0的流程和注意的坑

欢迎大家关注视频号,获取定期免费技术直播信息


《深入浅出MGR》视频课程

戳此小程序即可直达B站

https://www.bilibili.com/medialist/play/1363850082?business=space_collection&business_id=343928&desc=0



文章推荐:


想看更多技术好文,点个“在看”吧!

以上是关于建议收藏|MySQL DBA 防坑指南的主要内容,如果未能解决你的问题,请参考以下文章

Oracle 数据怎么实时同步到 MySQL | 亲测干货分享建议收藏

DBA整理的万字详解MySQL性能优化,值得收藏!

DBA整理的万字详解MySQL性能优化,值得收藏!

Navicat远程连接MySQL8,必知防坑策略

熬夜爆肝整理!史上最硬核的Mysql函数大全,还不收藏?

MySQL性能测试工具之mysqlslap使用详解