查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。
有些客户端连接框架会默认连接成功后先执行一个 set autocommit=0 的命令。这就导致接下来的查询都在事务中,如果是长连接,就导致了意外的长事务。因此,我会建议你总是使用 set autocommit=1, 通过显式语句的方式来启动事务:
按照 innodb_io_capacity 定义的能力乘以 R% 来控制刷脏页的速度。R脏页比例以及 redo log 写盘速度的最大值,一般不超过75%。
表中的数据被删除了,但是表空间却没有被回收。delete 命令其实只是把记录的位置,或者数据页标记为了“可复用”,但磁盘文件的大小是不会变的。此外插入数据也会造成这种空洞现象。
解决措施重建表:
alter table A engine=InnoDB
此时表A不能更新,否则就会有数据丢失。MySQL5.6后引入ONLINE DDL,对操作流程进行了优化,允许增删改操作,使用gh-ost:一个开源的缩小表空间工具
改进:创建联合索引或者覆盖所有字段的联合索引,这里并不是 说要为了每个查询能用上覆盖索引,就要把语句中涉及的字段都建上联合索引,毕竟索引还是有维护代价的。这是一个需要权衡的决定。
select count(*) from tradelog where month(t_modified)=7;
为什么条件是 where t_modified='2018-7-1’的时候可以用上索引,而改成 where month(t_modified)=7用不上索引?
对索引字段做函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索功能。需要注意的是,优化器并不是要放弃使用这个索引。而是放弃了树搜索功能,优化器可以选择遍历主键索引,也可以选择遍历索引 t_modified,优化器对比索引大小后发现,索引 t_modified 更小,遍历这个索引比遍历主键索引来得更快。因此最终还是会选择索引 t_modified。
解决方法:由于在 t_modified 字段加了 month() 函数操作,导致了全索引扫描。为了能够用上索引的快速定位能力,我们就要把 SQL 语句改成基于字段本身的范围查询。
mysql> select count(*) from tradelog where -> (t_modified >= '2016-7-1' and t_modified<'2016-8-1') or -> (t_modified >= '2017-7-1' and t_modified<'2017-8-1') or -> (t_modified >= '2018-7-1' and t_modified<'2018-8-1');
避免使用隐式类型转换,就用不上索引
mysql> select * from tradelog where tradeid=110717;
交易编号 tradeid 这个字段上,本来就有索引,但是 explain 的结果却显示,这条语句需要走全表扫描。你可能也发现了,tradeid 的字段类型是 varchar(32),而输入的参数却是整型,所以需要做类型转换。字符串和数字做比较的话,是将字符串转换成数字。
就知道对于优化器来说,这个语句相当于:
mysql> select * from tradelog where CAST(tradid AS signed int) = 110717;
这条语句触发了我们上面说到的规则:对索引字段做函数操作,优化器会放弃走树搜索功能。
避免两个表的字符集不同,一个是 utf8,一个是 utf8mb4,所以做表连接查询的时候用不上关联字段的索引。同样也是使用到了函数转换用不上树搜索功能。
通常我们说 MySQL 的“双 1”配置,指的就是 sync_binlog 和 innodb_flush_log_at_trx_commit 都设置成 1。也就是说,一个事务完整提交前,需要等待两次刷盘,一次是 redo log(prepare 阶段),一次是 binlog。这样才能保证数据的完整性。
如果执行的语句是 select ID from T where k between 3 and 5,需要查 ID 的值,而 ID 的值已经在 k 索引树上了,因此可以直接提供查询结果,不需要回表。也就是说,在这个查询里面,索引 k 已经“覆盖了”我们的查询需求,我们称为覆盖索引。由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。
场景:不断的删除历史数据和新增数据可能导致MySQL选错索引
原因:选择索引是优化器的工作,扫描行数并不是唯一的判断标准,优化器还会结合是否使用临时表、是否排序等因素进行综合判断。
根据统计信息判断,统计信息就是索引的区分度,索引上不同的值越多,区分度越好。而一个索引上不同的值的个数,我们称之为“基数”(cardinality)。也就是说,这个基数越大,索引的区分度越好。MySQL 是通过采样统计得到索引的基数
explain +语句 产看统计信息 analyze table 索引信息统计不准确解决方法
直接创建完整索引,这样可能比较占用空间;
创建前缀索引,节省空间,但会增加查询扫描次数,并且不能使用覆盖索引;
倒序存储,再创建前缀索引,用于绕过字符串本身前缀的区分度不够的问题;
创建 hash 字段索引,查询性能稳定,有额外的存储和计算消耗,跟第三种方式一样,都不支持范围扫描。
如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。热点行更新的解决策略: 降低并发度 1. 拆行,一行拆多行 2. Server 层限流,即同一时间进入更新的线程数 3. 关闭死锁监测(关闭的弊端是可能超时较多)
正常的短连接模式就是连接到数据库后,执行很少的 SQL 语句就断开,下次需要的时候再重连。如果使用的是短连接,在业务高峰期的时候,就可能出现连接数突然暴涨的情况。
错误方法:加大连接数会导致系统的负载可能会进一步加大,大量的资源耗费在权限验证等逻辑上,结果可能是适得其反,已经连接的线程拿不到 CPU 资源去执行业务的 SQL 请求。
第一种方法:先处理掉那些占着连接但是不工作的线程。在 show processlist 的结果里,踢掉显示为 sleep 的线程,可能有损。
第二种方法:减少连接过程的消耗,是让数据库跳过权限验证阶段。跳过权限验证的方法是:重启数据库,并使用–skip-grant-tables 参数启动。这样,整个 MySQL 会跳过所有的权限验证阶段,包括连接过程和语句执行过程在内 ,风险极高。
原因有三:索引没有设计好;SQL 语句没写好;MySQL 选错了索引。
导致慢查询的第一种可能是,索引没有设计好。这种场景一般就是通过紧急创建索引来解决。MySQL 5.6 版本以后,创建索引都支持 Online DDL 了,对于那种高峰期数据库已经被这个语句打挂了的情况,最高效的做法就是直接执行 alter table 语句。
导致慢查询的第二种可能是,语句没写好。可以通过改写 SQL 语句来处理。MySQL 5.7 提供了 query_rewrite 功能,可以把输入的一种语句改写成另外一种模式。比如,语句被错误地写成了 select * from t where id + 1 = 10000,你可以通过下面的方式,增加一个语句改写规则。
mysql> insert into query_rewrite.rewrite_rules(pattern, replacement, pattern_database) values ("select * from t where id + 1 = ?", "select * from t where id = ? - 1", "db1"); call query_rewrite.flush_rewrite_rules();
这里,call query_rewrite.flush_rewrite_rules() 这个存储过程,是让插入的新规则生效,也就是我们说的“查询重写”。
导致慢查询的第三种可能是,MySQL 选错了索引,已经在前文提及。
有时候由于业务突然出现高峰,或者应用程序 bug,导致某个语句的 QPS 突然暴涨,也可能导致 MySQL 压力过大,影响服务。可能是由一个新功能的 bug 导致的。当然,最理想的情况是让业务把这个功能下掉,服务自然就会恢复。
当然,这个操作的风险很高,需要你特别细致。它可能存在两个副作用: