2

我在 MySQL 中有两个表,“apps”和“icons”,每个表大约有 750K 行。在 Hibernate 中,我将它们建模为:

public class App {
    @Basic
    private String title;

    @OneToOne(mappedBy = "app")
    private Icon icon;
    // etc...
}

public class Icon {
    @Basic
    private String name;

    @OneToOne
    private App app;
    // etc...
}

当我添加这个关系时,我很快遇到了一个性能问题——在单个应用程序中读取需要超过 1 秒。我检查了 Hibernate 生成的 SQL,发现它是这样加入的:

select
    apps.id as app_id,
    apps.title as app_title,
    icons.id as icon_id,
    icons.name as icon_name
from
    apps
left outer join
    icons
        on apps.id=icons.app_id 
where
    apps.id="zyz";

我发现添加@Fetch(FetchMode.SELECT)注释大大提高了性能,将其降低到大约 30 毫秒,从而有效地获得相同的结果。这是生成的带有@Fetch(FetchMode.SELECT)注释的 SQL:

select
    apps.id as app_id,
    apps.title as title
from
    apps 
where
    apps.id="xyz";


select
    icons.id as icon_id,
    icons.name as icon_name
from
    icons
where
    icons.app_id="xyz";

为什么左外连接这么慢?连接查询上的“解释”显示:

+----+-------------+-------+-------+---------------+---------+---------+-------+--------+-------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows   | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+--------+-------+
|  1 | SIMPLE      | apps  | const | PRIMARY       | PRIMARY | 767     | const |      1 |       | 
|  1 | SIMPLE      | icons | ALL   | NULL          | NULL    | NULL    | NULL  | 783556 |       | 
+----+-------------+-------+-------+---------------+---------+---------+-------+--------+-------+

所以它显然是访问每一行,而不是多选查询的单行。连接不能使用我在 icons.app_id 上的索引吗?

PS:是的,我在计时运行之间使用了“RESET QUERY CACHE”。

更新:移动到一个bigint主键,用它来连接表而不是VARCHAR,并且连接的性能现在与“多选”方法相当。

4

1 回答 1

2

根据您的解释,我认为问题出在数据库层而不是应用程序层的架构上。

连接到图标表没有条目的事实possible_keys使我相信您正在运行没有 FK 约束的 MyISAM 存储引擎或 InnoDB 存储引擎。此外,767 的密钥长度让我觉得不寻常,我只见过这个值 < 10。

  • 如果引擎是 MyISAM:向列添加索引icons.app_id。并考虑使用 InnoDB 引擎,这样您就可以建立 FK 约束,这样您就不会以孤立的行结束。

  • icons.app_id如果引擎是 InnoDB:向which references添加 FK 约束apps.id。通过添加 FK 约束,您不仅可以确保数据不会成为孤立数据,还可以优化表之间的连接,因为您被迫在两列上创建索引。

上述任何一种解决方案都应该大大提高您的性能。让我知道事情的后续。

您可以使用以下链接阅读有关某些讨论主题的更多信息:

- 更新 -

当您准备好添加 INT 列时,这里有一些示例更改,请记住首先在 dev 上执行此操作,并确保在推送到生产之前解决问题。

对于应用程序表:

 ALTER TABLE apps
     ADD COLUMN idi int(11) UNSIGNED auto_increment FIRST,
     DROP PRIMARY KEY,
     ADD PRIMARY KEY(idi);

对于图标表:

 ALTER TABLE icons
     ADD COLUMN app_idi int(11) UNSIGNED auto_increment AFTER app_id,
     ADD INDEX (app_idi ),
     ADD FOREIGN KEY (app_idi) REFERENCES apps(app_idi) ON DELETE CASCADE;

这些改变只是示范性的,但应该足以让你朝着正确的方向开始。您可以阅读我发布的关于外键约束的 MySQL 文档以获取更多信息。现在,通过在应用程序和图标之间设置 FK 约束,如果删除了任何应用程序,与图标匹配的行app.id也将被删除,确保您不会孤立任何数据。如果您不想删除icons表中的关联行,您可以更改ON DELETE CASCADEON DELETE NULL,它们将与apps表取消链接,但仍驻留在icons表中。

于 2012-04-21T00:33:27.153 回答