如何在MySQL上加快“显示列”的速度?


7

我的应用程序依赖于为某些表运行“显示列”。运行大约需要60毫秒,而我们所有其他查询都需要不到1毫秒。information_schema直接查询甚至更慢。

该数据库包含约250个数据库,每个数据库100至200个表(总计约2万个表)。

  • 如何找出这些操作为何如此缓慢?
  • 也许我可以更改某些设置以使其运行更快,或将其缓存到SQL端吗?

(该应用程序每页面加载大约执行14个这样的查询-我很清楚,这个旧代码需要清理,但是在进行长期修复时会寻找可能的选项。)


1
出于兴趣,在什么情况下60ms太慢而无法检查表的列?这不是您应该执行的所有请求

1
显示栏是什么意思?从表中获取列名称还是打印整个列?如果它的名字是..为什么不抓一次它并将其存储在您的应用程序中呢...如果那不可能,为什么不创建另一个包含该表所有列的表呢?

@Jaitsu:不,这不是我们应该做的事情,但是,事实就是这样。旧版代码。在没有时间清理并正确执行之前,我想看看是否可以加快速度。我大约有14个可以在每次页面加载时运行。

@FlorinStingaciu:是的,列名。将它们放到另一个表中可能会加快处理速度,但是会不同步,这会破坏直接询问表的整个目的。

1
@Mat:这不是一个坏主意。投票决定要迁移到dba。

Answers:


12

MySQL重新计算某些访问INFORMATION_SCHEMA表的操作的表统计信息(SHOW COLUMNS只是查询的方便别名INFORMATION_SCHEMA.COLUMNS)。将innodb_stats_on_metadata设置为false,这将防止在您从表中请求元数据时发生这种重新计算。

SET GLOBAL innodb_stats_on_metadata=0;

并将以下内容添加到 my.cnf

[mysqld]
innodb_stats_on_metadata = 0

我应该提到我实际上正在使用MyISAM。尝试设置此设置,但没有带来任何好处。
mpen 2012年

您是否考虑过ALTER TABLE foo ENGINE = InnoDB?:)使用MyISAM是否有充分的理由?
亚伦·布朗

我认为,传统原因主要是。恐怕尝试这样做会发生什么事。不确定所有FK是否会排队。我会再考虑一下。
mpen 2012年

@AaronBrown +1对此答案,因为使用全InnoDB数据库面对这种情况的人都需要此信息。
RolandoMySQLDBA 2012年

1
+1放在[mysqld]那儿。对于许多人来说,此设置可能在mysqld下可能是显而易见的,但对于那些问这个问题的人来说可能并不明显。顺便说一句,这SELECT COUNT(*)在我的一张information_schema桌子上从一分钟多加速到了6秒。仍然很慢,但是有很大的进步。
Buttle Butkus 2013年

3

我建议您创建一个数据库,该数据库具有INFORMATION_SCHEMA表(或仅需要的表)作为副本。适当索引它们,您将获得性能提升。

但是,在此数据库之间进行同步的问题INFORMATION_SCHEMA比较棘手。

您可能有一个每小时或每5分钟同步一次这些表的过程(表的结构多久更改一次?)。

另一个想法是使用MySQL代理来捕获任何ALTER TABLE语句(以及CREATEDROP以及CREATE INDEX任何其他语句修改您需要的信息),然后在这些语句成功后同步复制的信息模式。


如果只需要列名,而不需要任何其他信息,例如数据类型,长度或可用索引,则可以SHOW COLUMNS用(快速)查询代替仅返回1行,LIMIT 1根本不返回任何行的查询,或者将其中一个替换为LIMIT 0

SELECT * FROM TableName WHERE FALSE ;

尽管普遍建议不要使用SELECT *,但这可能是合法的情况,没有其他用途。(除了*,其他所有内容都可能导致错误!)


2

在这种情况下,我认为这INFORMATION_SCHEMA是一条红鲱鱼。从我自己的SHOW COLUMNS性能测试来看,该innodb_stats_on_metadata变量在MyISAM或InnoDB表上似乎没有任何区别。

但是,从MySQL 5.0手册 ...

在某些情况下,无法使用内存中的临时表,在这种情况下,服务器将使用磁盘上的表来代替:

[...]

  • SHOW COLUMNS和的DESCRIBE语句中使用BLOB作为用于某些列的类型,从而用于结果的临时表是磁盘上的表。

自MySQL 5.5起,这似乎已从手册中删除,但仍适用于该版本...

mysql> SHOW VARIABLES LIKE 'version';
+---------------+-------------------------+
| Variable_name | Value                   |
+---------------+-------------------------+
| version       | 5.5.41-0ubuntu0.14.04.1 |
+---------------+-------------------------+
1 row in set (0.00 sec)

mysql> SHOW STATUS LIKE 'Created_tmp_disk_tables';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Created_tmp_disk_tables | 0     |
+-------------------------+-------+
1 row in set (0.00 sec)

mysql> SHOW COLUMNS FROM mysql.user;
[...snip...]
42 rows in set (0.00 sec)

mysql> SHOW STATUS LIKE 'Created_tmp_disk_tables';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Created_tmp_disk_tables | 1     |
+-------------------------+-------+
1 row in set (0.00 sec)

与查询结果集一起返回的字段信息包含与所返回的信息相同的信息SHOW COLUMNS,因此a SELECT * FROM my_table LIMIT 0应该实现相同的目的而无需为每个查询创建磁盘上的临时表。

一个简单的示例,仅需获取PHP中的字段名称...

$mysql = new mysqli('localhost', 'root', '', 'my_database');
$field_names = array();
$result = $mysql->query("SELECT * FROM my_table LIMIT 0");
$fields = $result->fetch_fields();
foreach ($fields as $fields)
{
    $field_names[] = $field->name;
}
var_dump($field_names);

以这种方式检索字段信息比较难于解码。您必须查阅底层MYSQL_FIELD结构的描述才能提取数据类型和标志,但是在我的系统上它的运行速度大约快7倍。


1

我喜欢@yerpcube的答案(+1)中的第一个建议,但我想提出一些建议

  • 在端口3307上创建另一个数据库实例
  • mysql使用以下选项将生产数据库转储到SQL文本文件中:
    • --no-data
    • --routines
    • --triggers
    • --all-databases--databases后跟您想要的数据库列表
  • 将SQL文本文件加载到端口3307 MySQL实例中

因此,您的mysqldump应该如下所示:

mysqldump --no-data --routines --triggers --all-databases > ImportFile.sql

而已。展望未来,您所需要做的就是连接到此端口3307数据库实例,并对您的内容进行任何与模式相关的查询。如果您知道生产数据库中的任何表发生了变化,只需mysql从生产中转储模式,然后将其重新加载到端口3307实例中即可。

警告:如果将mysql实例与生产版本安装在同一台计算机上,请绝对确保使用以下方式连接到该实例:

mysql -u... -p... -h127.0.0.1 -P3307 < ImportFile.sql

如果执行

mysql -u... -p... -P3307 < ImportFile.sql

它将生产软管。所以,要小心!

另一种选择是仅使用单独的数据库服务器。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.