转眼间,毕业N年了,最近一直反思自己,工作这么多年,都没有好好总结一下,所以从今天开始,我觉得应该总结一下,本着分享、共勉、学习的精神与大家一起进步。

那么今天我们就能分享一下我在mysql的遇到的坑,第一篇博文,有啥不好或者说的不对的,还请大家多多包涵。

一:主从分离的坑

这个其实不是我遇到的,而是我的同事遇到的,她做的支付系统,支付完了要去查询余额,因为我们是读写分离的,那么写入的表是主库,而我们去查询的表是从库,所以主从不一致的时候(这里同事查询是说主从同步延时了),然后我们同事为了把bug解决,用了一个简单粗暴的方法:直接从主库去查。顺便说一下,之后我去外面大厂面试的时候,面试官还就这个问题问我们的解决方案,当时我还喜滋滋的说,我们切成主库去查询。然后面试官笑而不语,现在想来是多么的low啊,主从、主从就是想读写分离啊,这样还有什么意义啊。所以所以所以我的面试就没有下文了,然后与大厂无缘了。

后来我回去也反思一下,真的除了我同事的办法,没有别的办法了吗?然后在想,是不是可以把数据通过redis缓存起来,读取的时候,读缓存,读到缓存就直接返回,读不到再去读数据库。这个方案我感觉到最后会一直把redis当成缓存用了,而且数据更新频繁的话,redis也会一直变化。也就是引发后面网上热门话题:热点key的更新问题,例外还有可能出现redis和mysql的数据不一致的问题。

现在偶然从某大咖的文章上,提供了一个思路,他的思路就是添加加缓存,缓存的有效期设置为主从不一致的时间,然后去查询的时候,可以先去查缓存,如果缓存有,就去取缓存的,如果没有就直接取出。这个方案比我之前想的还要优化一些,设置了缓存的有效期,这样就大量节约了缓存的内存空间,也可以一定程度上降低了热点key的更新了、在出现mysql和缓存数据不一致的时候尽可能查询数据库的数据

当然网上也有如下的解决方案【这些可能就需要DBA操作了】:
1、最简单的减少slave同步延时的方案就是在架构上做优化,尽量让主库的DDL快速执行。还有就是主库是写,对数据安全性较高,比如 sync_binlog=1,innodb_flush_log_at_trx_commit = 1 之类的设置,而slave则不需要这么高的数据安全,完全可以讲sync_binlog设置为0或者关闭binlog,innodb_flushlog也 可以设置为0来提高sql的执行效率。另外就是使用比主库更好的硬件设备作为slave。
2、提升主从服务器硬件性能
3、使用MySQL5.6.3以后的版本,因为mysql-5.6.3已经支持了多线程的主从复制。

当然大家还有什么比较好的方案呢?

二:分区表的坑

分区表的概念我就不介绍了,分区表的好处也无需我介绍了,分区表概念就是:表还是在一张表,但是实际存储的时候是分区的,所以跟我们真正的分表区别就是:分区表对外的感觉还是一张表,而且查询的如果用到分区键,会提高性能,因为只读取某个物理分区,注意:我这里用的是感觉,为啥是感觉呢,因为跟真正的没有分区的表还是有区别的。这里区别就是,我们查询的时候需要用到分区键。没有用到分区键白搭,还是全表扫描。甚至还会出现异想不到的结果。

这个是我的项目亲身经历,当时我是毕业4年左右吧,面试进入当时在游戏行业还是可以的一家游戏公司,自己也是新人意气风发,想要做出一份作为,然后老大就安排了我和一个同济毕业的小哥哥一起开发,【这里我为啥特意加上同济呢,因为人家就是优秀,我跟他一起合作开发这个项目学习不少】,我们项目是论坛,具体项目背景就不介绍了,论坛肯定有发帖吧,所以我们发帖表肯定有一张表,我们叫是t_thread,当然我已经介绍我当时入职时一家游戏公司,所以我们论坛发帖肯定不止一款游戏用吧,游戏我们是用app_id区分的,不同的游戏数据不影响,然后我们小哥哥设计的表是以app_id为分区键,所以我也就是这时候知道还有分区表这个高大上的概念,然后呢,我们就愉快的码代码了,正常情况下,我们筛选都根据游戏区分吧,也就是所有的查询语句都有app_id去查询,我们开发也一直相安无事。突然我开发到一个需求,pc的论坛首页,也就是各个游戏论坛的入口啦,然后我们有个热点动态排名,我们就是需要查询在所有游戏去查询,也就是没有app_id这个条件,然后我就写了类似这样的sql语句:select content from t_thread where status=1 order by reply_num desc limit 10【没毛病吧, 我们排名假设以回复数排名】,但是神奇的是,我死活查不来结果,然后我在mysql-client 直接执行这个语句,还是没有结果,【这个有个前提:因为内每个游戏也有单独的前10的排名, sql:select content from t_thread where status=1 and app_id=XXXX

三:高并发的坑

这个坑比较奇葩,首页项目已经上线了,并对接了2款游戏了,然后再次对接了公司的一个量级比较大的游戏,对接后,发现每隔一段时间我的服务就出现500异常,然后我就去服务器看log了,因为这个项目是我一个人开发的,所以就只能我一个人看了,我们项目是php + mysql +nginx,没有什么花哨,然后自然而然我就去查日志log了,先去查的nginx的error_log,然后错误我忘记了,反正百度一大堆,大概就是服务器不够用了,并发量超过了,需要扩展服务器了,然后老大说,让我给出依据,对啊,我总不能说网上说的是这个原因,就说并发量高了,需要扩展机器了【研发分配的项目标配:是2台机器服务器负载均衡】,没办法我就是想办法找依据了,然后想到access_log,业务场景介绍一下:就是我这里有个业务是统计玩家在线时长,由于在线时长是游戏方才有,所以需要他们对接我们接口。然后我就统计每秒钟访问的量级,命令:cat log文件 | grep 时间 | grep  接口名  | wc -l,然后依据就出来了,出问题的时间点每秒钟的并发量都是1.5W并发量,而且是每台机器上,然后已经超过ngnix的并发处理能力了。然后就扩展了4台机器。但是好景不长,后面还是反馈我的服务500了,这次锅大了,扩展了服务器还异常,所以我到现在都能感觉到leader的对我的不满的表情,我也心里郁闷啊。我只能继续查,然后我就是去看php的errorlog,通过分析php的error的发现,好像数据库连接问题,还好当时当时公司的DBA比较好,我跟DBA反馈后,他们立马帮我看了,说我的mysql操作的频率超过mysql处理的极限了,接口肯定还是同样的接口,因为只有这个接口访问很大,并发量大,所以我只能优化这个逻辑了。

看到这里了,聪明机智的大家肯定知道我在设计时实没有考虑量大的情况,因为我设计的接口是一条记录一条记录,假如换成现在去设计,我首先考虑接口是不是支持批量入库,或者用消息队列解耦了。

当时的优化方案并没有选择这两种,第一种,因为接口已经提供出去了,对接方已经对接上线了,所以不可能改成批量调用我们的接口了,而消息解耦,这个确实是我当时技术原因没有考虑到,现在想想确实用消息队列处理,效果更好。我们采取的方法是是比较笨的方法,就是少查数据库,因为我的业务逻辑是:有个判断的,就是针对已经绑定微信的用户才会记录,一开始我就是直接去查询数据库去判断用户有没有绑定,而根据我们的数据库的数据统计,50w用户,只有10w用户绑定了,后来改成:判断用户是否绑定,改从redis的判断,我用的set来保存已经绑定的用户,这样的话降低了数据库的并发访问量。