最近刚把MySQL数据库升级到8.0版本,开个帖子记录一下遇到的各种坑。
1、select count查询无法使用二级索引,导致查询性能比5.x版本慢很多
如果不带查询条件或者查询条件中没有用到二级索引,select count总是使用聚簇索引执行全表扫描,当表的数据量很大时,查询将非常耗时。而之前的5.x版本,MySQL将自动选择ken_len最小的二级索引进行计算,如果没有二级索引,才会选择聚簇索引。该问题,有人已经发现并反馈给MySQL,参见select count not using secondary index。
官方的回答:
InnoDB is a MVCC storage engine, which means that each transaction sees only changes within its scope. Hence a row count, that is visible to a transaction, can and does differ between sessions. This occurs in auto_commit mode as well, since it has in it inherent BEGIN / COMMIT statements.
Therefore, the only way for a transaction to know EXACTLY how many rows are there is to count all rows. When you count all rows, you can’t use indices, since sequential counting is much faster.
重点是最后一句:当你需要count所有记录,你不能使用索引,因为全表扫描更快。
全表扫描,需要加载整个聚簇索引,占用大量的innodb buffer,如果innodb buffer不够,还会导致频繁的磁盘IO,速度可比使用二级索引慢太多了,如果表的数据量巨大,还可能把数据库搞死。不知道MySQL开发人员是怎么想的,搞不懂,欢迎大神指教。
现阶段,只能修改查询逻辑,尽量避免count操作,或者尽量使用带二级索引查询条件的count操作。
2、Out of sort memory错误
今天发现一些SQL报错“ERROR 1038 (HY001): Out of sort memory, consider increasing server sort buffer size”,之前版本没有问题,google了一下发现了这个BUG https://bugs.mysql.com/bug.php?id=103225,官方的回复说这不是一个BUG,是一个新特性:
A potentially relevant change is that somewhere between those versions, we started sorting small blobs, such as TEXT, as addon fields instead of always doing sort-by-rowid. This is the reason why there’s now more pressure on the sort buffer (but for most cases, sorts should still be faster).
在8.0的某个版本,MySQL将text字段也保存到sort buffer中,导致8.0版本需要配置比之前版本更大的sort buffer,至于需要配置多大,官方建议至少能够容纳15条记录。
At minimum the sort_buffer_size value must be large enough to accommodate fifteen tuples in the sort buffer
如果不想增加sort_buffer_size,也可以将text字段改为longtext类型,longtext类型与之前版本一样不会保存到sort buffer。
3、innodb临时表可能导致磁盘被写满
最近发现MySQL服务器会周期性的因为磁盘空间不足而挂掉,经排重,发现是MySQL数据目录下的#innodb_temp占用空间太大导致的。查阅相关资料得知,#innodb_temp目录是MySQL临时表空间的默认存储目录。
从MySQL 8.0.16开始,写到磁盘的临时表强制使用innodb存储引擎,不再能通过参数internal_tmp_disk_storage_engine进行设置,该参数被同步移除。
使用innodb存储引擎时,每个连接会话最多会被分配两个临时表空间,分别用于存储用户手动创建的临时表和自动创建的临时表,每个临时表空间对应#innodb_temp目录下的一个.ibt文件,在会话持续期间,临时表空间会根据使用量自动增长,没有上限,当会话结束时,分配给会话的临时表空间会被回收,占用的磁盘空间才会被释放。详细信息请查看官方文档:15.6.3.5 Temporary Tablespaces
这样就会带来一个问题,当一个数据库连接持续很长时间,该连接的临时表空间可能会占用大量的磁盘空间,这样的连接越多,占用的磁盘空间越大,最后导致磁盘被写满。
解决方案:
- 配置应用的数据库连接池,给连接的配置最长使用时间,超过该时间后主动关闭连接。
- 优化查询,尽量少生成临时表,特别是数据量巨大的临时表。