我的应用程序依赖于为某些表运行“显示列”。运行大约需要60毫秒,而我们所有其他查询都需要不到1毫秒。information_schema
直接查询甚至更慢。
该数据库包含约250个数据库,每个数据库100至200个表(总计约2万个表)。
- 如何找出这些操作为何如此缓慢?
- 也许我可以更改某些设置以使其运行更快,或将其缓存到SQL端吗?
(该应用程序每页面加载大约执行14个这样的查询-我很清楚,这个旧代码需要清理,但是在进行长期修复时会寻找可能的选项。)
我的应用程序依赖于为某些表运行“显示列”。运行大约需要60毫秒,而我们所有其他查询都需要不到1毫秒。information_schema
直接查询甚至更慢。
该数据库包含约250个数据库,每个数据库100至200个表(总计约2万个表)。
(该应用程序每页面加载大约执行14个这样的查询-我很清楚,这个旧代码需要清理,但是在进行长期修复时会寻找可能的选项。)
Answers:
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
[mysqld]
那儿。对于许多人来说,此设置可能在mysqld下可能是显而易见的,但对于那些问这个问题的人来说可能并不明显。顺便说一句,这SELECT COUNT(*)
在我的一张information_schema
桌子上从一分钟多加速到了6秒。仍然很慢,但是有很大的进步。
我建议您创建一个数据库,该数据库具有INFORMATION_SCHEMA
表(或仅需要的表)作为副本。适当索引它们,您将获得性能提升。
但是,在此数据库之间进行同步的问题INFORMATION_SCHEMA
比较棘手。
您可能有一个每小时或每5分钟同步一次这些表的过程(表的结构多久更改一次?)。
另一个想法是使用MySQL代理来捕获任何ALTER TABLE
语句(以及CREATE
和DROP
以及CREATE INDEX
任何其他语句修改您需要的信息),然后在这些语句成功后同步复制的信息模式。
如果只需要列名,而不需要任何其他信息,例如数据类型,长度或可用索引,则可以SHOW COLUMNS
用(快速)查询代替仅返回1行,LIMIT 1
根本不返回任何行的查询,或者将其中一个替换为LIMIT 0
:
SELECT * FROM TableName WHERE FALSE ;
尽管普遍建议不要使用SELECT *
,但这可能是合法的情况,没有其他用途。(除了*
,其他所有内容都可能导致错误!)
在这种情况下,我认为这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倍。
我喜欢@yerpcube的答案(+1)中的第一个建议,但我想提出一些建议
--no-data
--routines
--triggers
--all-databases
或--databases
后跟您想要的数据库列表因此,您的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
它将生产软管。所以,要小心!
另一种选择是仅使用单独的数据库服务器。