MySQL多连接速度非常慢

MySQL多连接速度非常慢,mysql,database,performance,Mysql,Database,Performance,这是一个问题,我不确定根本原因可能在哪里,所以我将提供详细信息和我想到的问题点。任何帮助都会很棒(如果你住在附近,我就请你喝啤酒)。我有这三张桌子: 实践: `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(125) NOT NULL, `description` text, `deleted` int(11) unsigned DEFAULT NULL, `created` timestamp NOT NULL DEFA

这是一个问题,我不确定根本原因可能在哪里,所以我将提供详细信息和我想到的问题点。任何帮助都会很棒(如果你住在附近,我就请你喝啤酒)。我有这三张桌子:

实践:

`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(125) NOT NULL,
`description` text,
`deleted` int(11) unsigned DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`created_by` varchar(70) NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`last_update_by` varchar(70) NOT NULL,
PRIMARY KEY (`id`),
KEY `name` (`name`)
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`practice_fk` int(11) unsigned NOT NULL,
`phone` char(12) DEFAULT NULL,
`fax` char(12) DEFAULT NULL,
`address` varchar(125) DEFAULT NULL,
`address_two` varchar(125) DEFAULT NULL,
`city` varchar(40) NOT NULL,
`state` char(2) NOT NULL,
`zip` char(5) DEFAULT NULL,
`lat` decimal(7,5) DEFAULT NULL,
`lng` decimal(7,5) DEFAULT NULL,
`deleted` int(11) unsigned DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`created_by` varchar(70) NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`last_update_by` varchar(70) NOT NULL,
`email` varchar(150) DEFAULT NULL,
`practice_name_temp` text,
PRIMARY KEY (`id`)
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`location_fk` int(11) unsigned NOT NULL,
`practice_fk` int(11) unsigned NOT NULL,
`fname` varchar(25) NOT NULL,
`lname` varchar(45) NOT NULL,
`phone` varchar(35) DEFAULT NULL,
`mobile` char(12) DEFAULT NULL,
`email` varchar(125) DEFAULT NULL,
`title` varchar(100) DEFAULT NULL,
`description` text,
`deleted` int(11) unsigned DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`created_by` varchar(70) NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`last_update_by` varchar(70) NOT NULL,
PRIMARY KEY (`id`)
位置:

`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(125) NOT NULL,
`description` text,
`deleted` int(11) unsigned DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`created_by` varchar(70) NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`last_update_by` varchar(70) NOT NULL,
PRIMARY KEY (`id`),
KEY `name` (`name`)
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`practice_fk` int(11) unsigned NOT NULL,
`phone` char(12) DEFAULT NULL,
`fax` char(12) DEFAULT NULL,
`address` varchar(125) DEFAULT NULL,
`address_two` varchar(125) DEFAULT NULL,
`city` varchar(40) NOT NULL,
`state` char(2) NOT NULL,
`zip` char(5) DEFAULT NULL,
`lat` decimal(7,5) DEFAULT NULL,
`lng` decimal(7,5) DEFAULT NULL,
`deleted` int(11) unsigned DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`created_by` varchar(70) NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`last_update_by` varchar(70) NOT NULL,
`email` varchar(150) DEFAULT NULL,
`practice_name_temp` text,
PRIMARY KEY (`id`)
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`location_fk` int(11) unsigned NOT NULL,
`practice_fk` int(11) unsigned NOT NULL,
`fname` varchar(25) NOT NULL,
`lname` varchar(45) NOT NULL,
`phone` varchar(35) DEFAULT NULL,
`mobile` char(12) DEFAULT NULL,
`email` varchar(125) DEFAULT NULL,
`title` varchar(100) DEFAULT NULL,
`description` text,
`deleted` int(11) unsigned DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`created_by` varchar(70) NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`last_update_by` varchar(70) NOT NULL,
PRIMARY KEY (`id`)
联系人:

`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(125) NOT NULL,
`description` text,
`deleted` int(11) unsigned DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`created_by` varchar(70) NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`last_update_by` varchar(70) NOT NULL,
PRIMARY KEY (`id`),
KEY `name` (`name`)
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`practice_fk` int(11) unsigned NOT NULL,
`phone` char(12) DEFAULT NULL,
`fax` char(12) DEFAULT NULL,
`address` varchar(125) DEFAULT NULL,
`address_two` varchar(125) DEFAULT NULL,
`city` varchar(40) NOT NULL,
`state` char(2) NOT NULL,
`zip` char(5) DEFAULT NULL,
`lat` decimal(7,5) DEFAULT NULL,
`lng` decimal(7,5) DEFAULT NULL,
`deleted` int(11) unsigned DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`created_by` varchar(70) NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`last_update_by` varchar(70) NOT NULL,
`email` varchar(150) DEFAULT NULL,
`practice_name_temp` text,
PRIMARY KEY (`id`)
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`location_fk` int(11) unsigned NOT NULL,
`practice_fk` int(11) unsigned NOT NULL,
`fname` varchar(25) NOT NULL,
`lname` varchar(45) NOT NULL,
`phone` varchar(35) DEFAULT NULL,
`mobile` char(12) DEFAULT NULL,
`email` varchar(125) DEFAULT NULL,
`title` varchar(100) DEFAULT NULL,
`description` text,
`deleted` int(11) unsigned DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`created_by` varchar(70) NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`last_update_by` varchar(70) NOT NULL,
PRIMARY KEY (`id`)
架构背后的基本思想是有一个实践列表。一个机构可以有多个位置,但一个位置如果不与一个机构关联,就不能存在。然后,一个机构也可以有多个联系人,但是一个联系人必须与一个机构和一个位置相关联。[这就是问题的一部分可能开始的地方]。因此,我有一个疑问:

SELECT DISTINCT p.id AS practice_id, 
                p.name, 
                l.id AS location_id, 
                address AS location_address, 
                l.phone AS disp_phone, 
                CONCAT(pc.fname, ' ', pc.lname) AS practiceContact, 
                CONCAT(lc.fname, ' ', lc.lname) AS locationContact,
                pcc.qty AS practice_only_contact_qty,
                lcc.qty AS location_contact_qty,
                (pcc.qty + lcc.qty) AS contactQty
            FROM practices p
            LEFT JOIN practice_locations l on l.practice_fk=p.id
            LEFT JOIN (
                SELECT count(id) AS qty, practice_fk 
                FROM practice_contacts 
                GROUP BY practice_fk
            ) pcc ON pcc.practice_fk=p.id
            LEFT JOIN practice_contacts pc ON pc.practice_fk=pcc.practice_fk AND pcc.qty=1
            LEFT JOIN (
                SELECT count(id) AS qty, location_fk 
                FROM practice_contacts 
                GROUP BY location_fk
            ) lcc ON lcc.location_fk=l.id
            LEFT JOIN practice_contacts lc ON lc.location_fk=lcc.location_fk AND lcc.qty=1
            WHERE p.name IS NOT NULL AND p.deleted IS NULL
            GROUP BY p.id
            ORDER BY p.name ASC, l.state, l.city, l.address;
这个查询应该做的是:

  • 收集机构ID和名称
  • 如果只有一个位置,请抓取它的地址。否则,获取第一个位置的地址
  • 如果有一个联系人与该机构相关,请记下他们的姓名。否则,请获取第一个联系人的姓名
  • 计算有多少联系人与该机构关联
  • 计算与该机构关联的位置数
  • 根据机构ID将所有这些信息分组,然后根据机构名称按字母顺序排列,然后按位置排列
所以,它现在就做到了这一切。真的很慢。当我在practices表中只有5条记录,而在其他两个表中只有不到20条记录时,查询工作得非常好。现在我已经将数据导入到这些表中(实践中有9000条记录,位置中有14000多条记录,联系人中有25000多条记录),这个查询需要28秒才能返回我需要的数据。如果我把小组拉出来,我们会看到33秒以上。去我的,对吧

显然,这是不可接受的。此数据集相对较小,并且此应用程序只会随着某个时刻可能存在的数百万联系人而增长。所以,我想知道这是否是一个由三部分组成的问题:

  • 第一部分:我应该引入一个参考表[有点像视图]来存储这些关系吗

    `id` int(11) unsigned not null,
    `practice_fk` int(11) unsigned not null,
    `location_fk` int(11) unsigned not null,
    `contact_fk` int(11) unsigned not null,
    PRIMARY KEY(id),
    KEY(practice_fk),
    KEY(location_fk),
    KEY(contact_fk)
    
    但是如果我这样做,我不确定如何构造查询以根据需要提取数据?它是否会提供任何性能优势

  • 第二部分:我没有适当的索引。在浏览了MySQL文档并结结巴巴地阅读了这篇文章()之后,我开始明白InnoDB是一只慢猪。从用户体验的角度来看,这是不可接受的,但从架构的角度来看,我被锁定在这个引擎中。如何正确设置索引以使查询返回到次秒范围

  • 第三部分:我的查询是垃圾。我认为这可能是最大的罪魁祸首。我仍在学习如何构造这些更复杂的SQL查询,这本身需要一些努力,因此任何关于如何使这件事不那么像猪的指针都会很好


我对我的查询尝试了各种各样的操作(删除groupby、删除orderby等等),几乎没有任何变化。查询始终以28到33秒的速度运行。任何指导都将不胜感激。

您可以使用行限制语法始终返回第一行,而不是计算联系人和练习的数量。我不能保证这将有助于表现,但这是两个少加入担心

        LEFT JOIN (
            SELECT * 
            FROM practice_contacts 
            GROUP BY practice_fk LIMIT 1
        ) pcc ON pcc.practice_fk=p.id
        LEFT JOIN (
            SELECT *
            FROM practice_locations
            GROUP BY practicec_fk LIMIT 1
        ) lcc ON lcc.practice_fk = p.id
我没有验证sql是否可以工作,但是您知道了。如果需要特定的联系人或位置(例如,最近的联系人或位置),可以在子选择中包含ORDER BY子句


请参见

并非所有这些问题都可以“修复”,但它们会向我发出性能危险信号:

  • 不要将
    不同的
    分组方式
    混在一起。他们也会做同样的事情
  • 不要使用InnoDB;你引用的链接遭到了断然驳斥——作者承认了这一点
  • 如果
    JOIN
    满足您的需求,请不要使用
    LEFT JOIN
    LEFT
    表示“right”表可能缺少行
  • 左连接(选择…
    通常无法优化,但
    连接可能会优化
  • 这尤其低效:
    (选择…)JOIN(选择…)
  • “爆炸内爆”:
    连接
    增加行数<代码>分组依据
    然后放气。这是性能问题的常见原因。(也许我可以更具体地说。)
  • COUNT(x)
    检查
    x
    是否为
    NULL
    。通常,您真正想要的是
    COUNT(*)
为了阅读的清晰性,以及使用
左连接时的“正确性”,请仅在
中的
中设置“连接”条件
;将“筛选”条件放入
中的
。我认为
和pcc.qty=1
应该从
ON
移动到
WHERE
。(我认为这可能会改变结果集。)

可能的索引:

p: INDEX(deleted, name, id)
l: INDEX(practice_fk)
执行解释选择…
。如果你没有看到“自动键”,那么你有一个旧版本的MySQL;考虑升级。“自动键”表示我对
(选择…)加入(选择…
的评论不适用。否则,考虑两个<代码>创建临时表< /代码>,并在<代码>……fk < /COD>中添加索引。然后使用tmp表,而不是
左连接(选择…
两次

对我的评论尽你所能,然后带着修改后的查询回来,再加上
解释
以获得进一步的评论(如果必要的话)


关于创建索引的更多信息:

看来您实际上不需要按子查询中的任何内容排序。所以,您可以显式地按NULL设置ORDER,以提高子查询的性能

修改的查询:

SELECT
        DISTINCT p.id AS practice_id,
        p.name,
        l.id AS location_id,
        l.address AS location_address,
        l.phone AS disp_phone,
        CONCAT(pc.fname,
        ' ',
        pc.lname) AS practiceContact,
        CONCAT(lc.fname,
        ' ',
        lc.lname) AS locationContact,
        pcc.qty AS practice_only_contact_qty,
        lcc.qty AS location_contact_qty,
        (pcc.qty + lcc.qty) AS contactQty 
    FROM
        practices p 
    LEFT JOIN
        practice_locations l 
            ON l.practice_fk = p.id 
    LEFT JOIN
        (
            SELECT
                COUNT(practice_contacts.id) AS qty,
                practice_contacts.practice_fk 
            FROM
                practice_contacts 
            GROUP BY
                practice_contacts.practice_fk 
            ORDER BY
                NULL
        ) pcc 
            ON pcc.practice_fk = p.id 
    LEFT JOIN
        practice_contacts pc 
            ON pc.practice_fk = pcc.practice_fk 
            AND pcc.qty = 1 
    LEFT JOIN
        (
            SELECT
                COUNT(practice_contacts.id) AS qty,
                practice_contacts.location_fk 
            FROM
                practice_contacts 
            GROUP BY
                practice_contacts.location_fk 
            ORDER BY
                NULL
        ) lcc 
            ON lcc.location_fk = l.id 
    LEFT JOIN
        practice_contacts lc 
            ON lc.location_fk = lcc.location_fk 
            AND lcc.qty = 1 
    WHERE
        p.name IS NOT NULL 
        AND p.deleted IS NULL 
    GROUP BY
        p.id 
    ORDER BY
        p.name ASC,
        l.state,
        l.city,
        l.address
另外,添加以下索引可能会优化查询:

ALTER TABLE `practices` ADD INDEX `practices_index_1` (`deleted`,`id`,`name`);
ALTER TABLE `practice_contacts` ADD INDEX `practice_contacts_index_1` (`practice_fk`,`location_fk`);
ALTER TABLE `practice_contacts` ADD INDEX `practice_contacts_index_2` (`location_fk`);
ALTER TABLE `practice_locations` ADD INDEX `practice_locations_index_1` (`practice_fk`,`id`);

你是否在你的练习之间创建了外键,locat