![5deec240c786a5720ffe7c297650cd99.png](https://i-blog.csdnimg.cn/blog_migrate/085b001b0f3d9486363dc99bdac9d676.jpeg)
1.访问数据库超时
判断故障是不是和访问量有关系 ->排查重点就应该放在服务用户访问的功能上,比如首页,商品列表页,内容推荐等功能
如果系统只是在访问量峰值的时候,请求超时,但是随着访问量减少,系统能够自动恢复,可以排除后台服务被大量请求打死的可能性。
MySQL总CPU利用率高的现象,绝大多数情况是由慢SQL导致的,可以分析慢SQL日志,是查找类似问题原因最有效的方法 ->分析慢SQL日志的时候,首先要找到一个特别慢的SQL
对于许多慢SQL,比如排行榜,一定要做缓存。
如果CPU利用率,呈周期性,有规律的波动就要考虑定时任务了
作为开发,我们应该
一、在编写SQL的时候,一定小心谨慎仔细评估
1.1 、SQL涉及到的表,数据规模是多少
1.2、SQL可能会遍历的数据量是多少
1.3、尽量避免写出慢SQL
二、能不能利用缓存减少数据库查询次数?在使用缓存的时候,特别需要注意的就是缓存命中率,要尽量避免请求命中不了缓存,穿透到数据库。
建议:
上线一个定时监控和杀掉慢SQL的脚本。这个脚本每分钟执行一次,检测一分钟内,又没有执行时间超过一分钟的慢SQL,如果有,直接杀掉
做一个简单的静态页面的首页作为降级方案。请求超时的时候返回这个静态页面,暂时降级。
- 避免写出慢SQL
一般一台MySQL服务器,平均每秒钟执行的SQL数量在几百左右,就已经算非常繁忙得了。
在编写一条查询语句的时候,可以根据要查询数据表的数据总量,估算一下这条查询大致需要遍历多少行数据。
a、使用索引避免全表扫描
虽然索引能够有效减少执行查询时遍历数据的行数,提高查询性能,但是代价就是会降低数据插入,删除和更新的性能。所以,对于更新频繁并且对于性能要求高的表,尽量少建索引。对于查询多的表,多建索引。
b、对于复杂的查询,最好分析SQL执行计划
使用EXPLAIN关键字执行查询语句
- SQL是如何在数据库中执行的
数据库的服务端,可以划分为执行器和存储引擎两部分。执行器负责解析SQL执行查询,存储引擎负责保存数据。
4.使用缓存保护MySQL
Redis可以作为MYSQL的前置缓存,可以替MySQL抵挡大部分查询请求,很大程度上环节MySQL并发请求的压力
a.更新缓存的最佳方式
Redis是一个使用内存保护数据的高性能的KV数据库,高性能主要在于
1.简单的数据结构
2.使用内存存储数据
内存是一种易失性存储,所以牺牲了大部分功能,牺牲了数据可靠性,换取了高性能。
虽然Redis支持将数据持久化到磁盘,并且支持主从复制,但是仍然是一个不可靠的存储,设计上就天然不保证数据的可靠性。所以我们使用Redis的时候,就要兼容Redis丢数据的情况,做到即使Redis发生了丢数据,也不影响数据准确性。
可以使用Cache Aside模式更新缓存,可以非常有效避免并发读写导致的脏数据问题。
5.读写分离
读写分离是提升MySQL并发的首选方案
读写分离中数据库应对的绝大部分请求都是只读查询请求,并且实施起来简单,把使用单机MySQL的系统升级为读写分离的多实例,只需要简单修改DAO代码,把对数据库的读写请求分开,请求不同的MYSQL实例就可以了
怎么实施MYSQL的读写分离方案
1.部署一主多从多个MYSQL实例,并让他们之间保持数据实时同步
2.分离应用程序对数据库的读写请求,分别发送到从库和主库
分离应用程序的读写请求方法有下面这三种(查看官网分档):
纯手工方式:修改应用程序的 DAO 层代码,定义读写两个数据源,指定每一个数据库请求的数据源。
组件方式:也可以使用像 Sharding-JDBC 这种集成在应用中的第三方组件来实现,这些组件集成在你的应用程序内,代理应用程序的所有数据库请求,自动把请求路由到对应数据库实例上。
代理方式:在应用程序和数据库实例之间部署一组数据库代理实例,比如说 Atlas 或者 MaxScale。对应用程序来说,数据库代理把自己伪装成一个单节点的 MySQL 实例,应用程序的所有数据库请求被发送给代理,代理分离读写请求,然后转发给对应的数据库实例。
读写分离的副作用:
可能存在数据不一致的情况
主从同步延迟会导致主库和从库之间出现数据不一致的情况,我们的应用程序应该能兼容主从延迟,避免因为主从延迟而导致的数据错误。规避这个问题最关键的一点是,我们在设计系统的业务流程时,尽量不要在更新数据之后立即去查询更新后的数据。
6.MySQL主从数据库同步
当客户端提交一个事务到 MySQL 的集群,直到客户端收到集群返回成功响应,在这个过程中,MySQL 集群需要执行很多操作:主库需要提交事务、更新存储引擎中的数据、把 Binlog 写到磁盘上、给客户端返回响应、把 Binlog 复制到所有从库上、每个从库需要把复制过来的 Binlog 写到暂存日志中、回放这个 Binlog、更新存储引擎中的数据、给主库返回复制成功的响应。
性能最好的方法是异步复制,主节点上先记录操作日志,再更新状态数据,然后异步把操作日志复制到所有从节点上,并在从节点执行操作日志,得到和主节点相同的状态数据。
异步复制的劣势是,可能存在主从延迟,如果主节点宕机,可能会丢数据。另外一种常用的策略是半同步复制,主节点等待操作日志最少成功复制到 N 个从节点上之后,再更新状态,这种方式在性能、高可用和数据可靠性几个方面都比较平衡,很多分布式存储系统默认采用的都是这种方式。
7.订单数据变多,数据库变慢怎么办
查询耗费的时间主要在两个因素
1.查找的时间复杂度
2.数据总量
a.存档历史记录数据提高查询性能
当单表的订单数据太多,多到影响性能的时候,首选方案是,归档历史订单(就是把大量的历史订单移到另外一张历史订单表中)
大致流程
![image-20200802104900478](/Users/zhaoyu/Library/Application Support/typora-user-images/image-20200802104900478.png)
1.首先创建一个和订单表结构一模一样的历史订单表
2.把订单表中的历史数据分批查出来,插入到历史订单表中
3.测试和上线支持历史订单表的新版本代码,查看是否有BUG,看是否需要回滚
4.等新版本代码上线并验证无误之后,就可以删除订单表中的历史订单数据了
5.上线一个定期迁移数据的程序或者脚本,定期吧过期订单从订单表搬到历史订单表中(迁移之前一定做好备份)
b.如何批量删除大量数据(一定要做好备份)
select max(id) from orders
where timestamp < SUBDATE(CURDATE(),INTERVAL 3 month);
delete from orders
where id <= ?
order by id limit 1000;
只需要先通过一次查询,找到符合条件的历史订单中最大的那个订单ID,然后在删除语句中吧删除条件换成主键删除,就不同每次都是时间戳上找符合条件的记录
加order by主要就是为了B+ 树的数据结构,这样删除更加快一点
对于平凡插入删除大量数据的这种表,如果能接受锁表,定期执行OPTIMIZE TABLE是非常有必要的。
8.分库分表
如何规划分库分表:能不拆就不拆,能少拆就不多拆
分库分表的目的主要解决两个问题
1.数据量太大查询慢的问题。解决查询慢,只要减少每次查询的数据总量就可以了,也就是,分表就可以解决问题
2.应对高并发的问题,一个数据库实例撑不住,就把并发请求分散到多个实例中去,所以,解决高炳达的问题需要分库
概括就是数据量大就分表,高并发就分库
如何选择 sharding key? 这是分表的依据,选择要考虑的因素取决于业务如何访问数据,让查询尽量落到一个分片中。如果分片无法兼容查询,可以把数据同步到其他存储中,供查询
常见的分片算法 1.范围分片 对查询友好,适合并发量不大的场景。但容易产生热点数据 2.哈希分片 比较均匀 3.查表法 灵活,性能差因为多了一次查表