对拥有一个几十万行表的 MySQL 性能优化的简单办法

本文围绕数据库优化展开,以MySQL为例,讲述对拥有几十万行表的group by优化。排除系统等因素后,核心是配置参数调整。通过分析slow log和EXPLAIN结果,发现问题与临时表有关,调整tmp_table_size参数后,数据库性能显著提升,还指出TMP空间充足是系统通用原则。

由 徐永久 发表于 2005年05月12日 00:30。数据库的优化大概是在系统管理中最具有挑战性的了,因为其对人员的素质要求几乎是全方面的,好的 DBA 需要各种综合素质。在排除了操作系统,应用等引起的性能问题以外,优化数据库最核心的实际上就是配置参数的调整。本文通过一个简单的参数调整,实现了对拥有一个几十万行表的 group by 优化的例子。通过这个简单的调整,数据库性能有了突飞猛进的提升。
本例子是针对 MySQL 调整的,不像其他商业数据库,MySQL 没有视图,特别是 Oracle 可以利用固化视图来提升查询性能,没有存储过程,因此性能的调整几乎只能通过配置合适的参数来实现。

调整的具体步骤(例子针对 pLog 0.3x 的博客系统):

发现最多的 slow log 是:
SELECT category_id, COUNT(*) AS 'count' FROM plog_articles WHERE blog_id = 2 AND status = 'PUBLISHED' group by category_id;
一般在 20s 以上,甚至 30s 。
而当 blog_id=1 或者其他时,都能很快的选出结果。
于是怀疑索引有问题,重新建立索引,但无济于事。 EXPLAIN 结果如下:
mysql> EXPLAIN SELECT category_id, COUNT(*) AS 'count' FROM plog_articles WHERE blog_id = 2 AND status = 'PUBLISHED' group by category_id;
+---------------+------+------------------+------------------+---------+-------------+------+----------------------------------------------+
| table | type | possible_keys | key | key_len | ref | rows | Extra |
+---------------+------+------------------+------------------+---------+-------------+------+----------------------------------------------+
| plog_articles | ref | idx_article_blog | idx_article_blog | 5 | const,const | 4064 | Using where; Using temporary; Using filesort |
+---------------+------+------------------+------------------+---------+-------------+------+----------------------------------------------+
1 row in set (0.00 sec)

于是想到每次查看 blog_id = 2 的博客时,系统负载就提高,有较高的 swap 。于是查看 temporary table 有关的资料,果然有这样的说法:

If you create a lot of disk-based temporary tables, increase the size of tmp_table_size if you can do so safely. Keep in mind that setting the value too high may result in excessive swapping or MySQL running out of memory if too many threads attempt to allocate in-memory temporary tables at the same time. Otherwise, make sure that tmpdir points to a very fast disk that's not already doing lots of I/O.
Another problem that doesn't show up in the slow query log is an excessive use of disk-based temporary tables. In the output of EXPLAIN, you'll often see Using temporary. It indicates that MySQL must create a temporary table to complete the query. However, it doesn't tell you whether that temporary table will be in memory or on disk. That's controlled by the size of the table and MySQL's tmp_table_size variable.
If the space required to build the temporary table is less than or equal to tmp_table_size, MySQL keeps it in memory rather than incur the overhead and time required to write the data to disk and read it again. However, if the space required exceeds tmp_table_size, MySQL creates a disk-based table in its tmpdir directory (often /tmp on Unix systems.) The default tmp_table_size size is 32 MB.
To find out how often that happens, compare the relative sizes of the Created_tmp_tables and Created_tmp_disk_tables counters:

调整 tmp_table_size为 80M 左右后,以上语句 14s 即可解决。

这个参数是 DBA 很容易忽视的。

其实,不单单是数据库,就是操作系统,也是受 tmp 的影响巨大,例如安装软件到 d: 盘,如果 TMP 环境变量指向 c: 盘,而 c: 空间不够,照样可能导致安装失败。

因此让 TMP 有足够的空间可以说是计算机系统里一个普遍适用的原则(写程序也是一样)。

要补充一下,前一段在做一个项目时也遇到了类似的问题。数据中记录不算多才十几万条。slow的原因也是一条语句影响。情况和以上类似。现在看来可能真是tmp_table_size参数的事。

<think>嗯,用户想了解如何为包含几十万篇文章的MySQL数据设计索引和结构优化。首先,我需要回忆一下关于MySQL索引和结构优化的基本知识。记得索引的作用是加快数据检索,而结构设计要考虑存储效率和查询性能。 用户提到数据量是几十万篇,这个量级不算特别大,但也不能忽视优化。首先,结构设计方面,可能需要考虑字段类型的选择。比如文章的标题用VARCHAR,长度要合适,不能太长,否则影响索引效率。内容字段可能需要用TEXT类型,并且分开存储,避免大字段影响查询速度。还有状态字段,比如是否发布,可以使用TINYINT或者ENUM,但ENUM的索引可能不如TINYINT高效,需要确认一下。时间字段用DATETIME或TIMESTAMP,索引这些字段对按时间查询有帮助。 然后是索引设计。主键肯定是自增ID,这有助于插入性能。唯一索引可能需要根据业务需求,比如文章标题是否唯一,但标题可能重复,所以可能不需要。组合索引要考虑常用的查询件,比如状态加上时间,或者分类加上时间。比如用户经常按分类和发布时间查询,组合索引会有效。全文索引的话,如果需要对内容进搜索,可能需要使用MySQL的全文检索或者引入Elasticsearch,但用户可能希望用MySQL自带的,所以需要提到。还需要考虑分分库的问题,不过几十万数据可能单还能应付,但需要考虑未来的扩展。垂直分的话,把内容字段单独放一张,减少主的大小。分区的话,按时间分区可能有用,特别是如果经常按时间范围查询的话。另外,索引维护也很重要,比如定期重建索引,避免碎片化。还有避免过度索引,因为太多索引会影响写入性能。需要提到哪些字段不适合加索引,比如区分度低的字段,比如状态字段只有几个值,可能不适合单独索引,但可以作为组合索引的一部分。用户的引用里提到了索引的数据结构,比如B+树,还有聚簇索引和非聚簇索引的区别。在设计索引时,需要利用聚簇索引的特性,主键查询会更快。非聚簇索引则需要回,可能会影响性能,所以覆盖索引的情况可以优化。可能还需要考虑查询的排序和分组,如果经常有ORDER BYGROUPBY,索引的设计要包含这些字段。比如常用的排序是按时间倒序,那么索引可以设计为分类+状态+时间,并且按降序排列。 另外,长字段的索引问题,比如对内容字段加索引是不现实的,这时候可能需要前缀索引,但效果可能有限,所以还是建议用全文索引或者其他解决方案。 关于存储引擎,InnoDB支持事务和级锁,适合写多读多的场景,而MyISAM可能在读多写少的情况下更快,但用户可能已经在用InnoDB,所以需要确认。还有字符集的选择,UTF8MB4支持更全的字符,但会占用更多空间,需要考虑存储和索引的大小影响。 最后,需要给出一个示例的结构,包括字段类型、索引设计,以及分的建议。可能还要提到监控和优化,比如使用EXPLAIN分析查询,定期检查慢查询日志。总结一下,结构设计要合理分字段,选择合适的类型,索引设计要覆盖常用查询件,避免过度索引,考虑分和分区,以及使用适当的存储引擎和字符集。同时维护和监控索引效果,及时调整优化。</think>### MySQL海量文章索引设计与结构优化指南 #### 一、结构设计原则 1. **字段类型优化**: - 标题使用$varchar(255)$且设置`COLLATE utf8mb4_general_ci`[^3] - 内容字段建议拆分为$text$类型单独存储(垂直分) - 状态字段使用$tinyint$而非$enum$,避免索引效率问题[^2] - 时间字段统一采用$datetime$格式并建立复合索引 2. **大字段分离策略**: ```sql CREATE TABLE article_content ( article_id BIGINT UNSIGNED PRIMARY KEY, content MEDIUMTEXT NOT NULL, FULLTEXT INDEX (content) -- 全文索引 ) ENGINE=InnoDB; ``` #### 二、核心索引设计 1. **主索引结构**: ```sql CREATE TABLE articles ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, category_id INT NOT NULL, status TINYINT NOT NULL DEFAULT 0, publish_time DATETIME NOT NULL, -- 其他元数据字段... KEY idx_category_status (category_id, status), KEY idx_publish_status (publish_time, status) ) ENGINE=InnoDB; ``` 2. **复合索引黄金组合**: - 高频查询组合:$category_id + status + publish_time$ - 时间范围查询:$publish_time + status$ 3. **覆盖索引优化**: ```sql ALTER TABLE articles ADD INDEX idx_covering (category_id, publish_time, status); ``` #### 三、分与分区策略 1. **水平分方案**: ```sql -- 按年份分 CREATE TABLE articles_2023 ( CHECK ( YEAR(publish_time) = 2023 ) ) INHERITS (articles); ``` 2. **分区优化查询**: ```sql ALTER TABLE articles PARTITION BY RANGE (YEAR(publish_time)) ( PARTITION p2020 VALUES LESS THAN (2021), PARTITION p2021 VALUES LESS THAN (2022), PARTITION p2022 VALUES LESS THAN (2023) ); ``` #### 四、性能调优要点 1. **索引维护策略**: - 定期执$OPTIMIZE TABLE$重建索引 - 使用$innodb_index_stats$监控索引使用率[^1] 2. **写入优化技巧**: - 关闭$autocommit$批量提交事务 - 设置$innodb_flush_log_at_trx_commit=2$ #### 五、查询优化示例 ```sql -- 优化后的查询语句 SELECT id, title FROM articles USE INDEX (idx_publish_status) WHERE publish_time BETWEEN '2023-01-01' AND '2023-12-31' AND status = 1 ORDER BY publish_time DESC LIMIT 100; ``` #### 六、监控与维护 1. **慢查询分析**: ```sql SET GLOBAL slow_query_log = ON; SET long_query_time = 1; ``` 2. **索引效率验证**: ```sql EXPLAIN SELECT * FROM articles WHERE category_id = 5 AND status = 1; ``` ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值