为什么 LEFT JOIN ON(...AND...) 这个查询会变慢?

flo*_*ank 5 mysql performance query-performance

我不明白为什么这个查询很慢。它运行 3-4 秒。当我删除表上的联接时,wa_file_storage整个查询运行时间不到 0.02 秒。explain没有表现出任何特别的东西,至少对我来说没有。我很可能缺乏知识,无法从解释中显示的数据得出正确的结论。我猜这与那里使用的两个索引和密钥长度有关?

我发现这两个条件的结合导致速度变慢:

ON (
    `CompanyLogo`.`model` = 'CompanyLogo' 
    AND 
    `Companies`.`id` =     (`CompanyLogo`.`foreign_key`)
)
Run Code Online (Sandbox Code Playgroud)

当我从中删除 CompanyLogo 或 Companies 条件时,查询再次快速运行。

为什么这个连接 ON 会导致查询性能变得如此糟糕,我该如何修复这种情况?

输出explain

在此输入图像描述

explain仅包含字段的输出foreign_key(运行23 秒!):

在此输入图像描述

explain仅包含字段的输出model(运行 0.015 秒):

在此输入图像描述

查询:

SELECT `Jobs`.`id` AS `Jobs__id`,
       `Jobs`.`company_id` AS `Jobs__company_id`,
       `Jobs`.`job_category_id` AS `Jobs__job_category_id`,
       `Jobs`.`branch_id` AS `Jobs__branch_id`,
       `Jobs`.`title` AS `Jobs__title`,
       `Jobs`.`reference_number` AS `Jobs__reference_number`,
       `Jobs`.`location_same_as_office_address` AS `Jobs__location_same_as_office_address`,
       `Jobs`.`country_id` AS `Jobs__country_id`,
       `Jobs`.`city` AS `Jobs__city`,
       `Jobs`.`type_id` AS `Jobs__type_id`,
       `Jobs`.`description` AS `Jobs__description`,
       `Jobs`.`contact_first_name` AS `Jobs__contact_first_name`,
       `Jobs`.`contact_last_name` AS `Jobs__contact_last_name`,
       `Jobs`.`contact_tel` AS `Jobs__contact_tel`,
       `Jobs`.`contact_fax` AS `Jobs__contact_fax`,
       `Jobs`.`contact_email` AS `Jobs__contact_email`,
       `Jobs`.`show_job_on_world_architects` AS `Jobs__show_job_on_world_architects`,
       `Jobs`.`total_hits` AS `Jobs__total_hits`,
       `Jobs`.`last_activated_on` AS `Jobs__last_activated_on`,
       `Jobs`.`active` AS `Jobs__active`,
       `Jobs`.`slug` AS `Jobs__slug`,
       `Jobs`.`created` AS `Jobs__created`,
       `Jobs`.`modified` AS `Jobs__modified`,
       `Jobs`.`logo` AS `Jobs__logo`,
       `Jobs`.`activation_counter` AS `Jobs__activation_counter`,
       `Jobs`.`deactivated_by_client` AS `Jobs__deactivated_by_client`,
       `Jobs`.`logo_id` AS `Jobs__logo_id`,
       `Countries`.`id` AS `Countries__id`,
       `Countries`.`country` AS `Countries__country`,
       `Companies`.`id` AS `Companies__id`,
       `Companies`.`company` AS `Companies__company`,
       `Companies`.`company2` AS `Companies__company2`,
       `Companies`.`company3` AS `Companies__company3`,
       `Companies`.`street` AS `Companies__street`,
       `Companies`.`street2` AS `Companies__street2`,
       `Companies`.`pobox` AS `Companies__pobox`,
       `Companies`.`postal_code` AS `Companies__postal_code`,
       `Companies`.`city` AS `Companies__city`,
       `Companies`.`country_id` AS `Companies__country_id`,
       `Companies`.`state_id` AS `Companies__state_id`,
       `Companies`.`tel1` AS `Companies__tel1`,
       `Companies`.`tel2` AS `Companies__tel2`,
       `Companies`.`mobile` AS `Companies__mobile`,
       `Companies`.`url` AS `Companies__url`,
       `Companies`.`url2` AS `Companies__url2`,

       `CompanyLogo`.`id` AS `CompanyLogo__id`,
       `CompanyLogo`.`user_id` AS `CompanyLogo__user_id`,
       `CompanyLogo`.`foreign_key` AS `CompanyLogo__foreign_key`,
       `CompanyLogo`.`model` AS `CompanyLogo__model`,
       `CompanyLogo`.`filename` AS `CompanyLogo__filename`,
       `CompanyLogo`.`filesize` AS `CompanyLogo__filesize`,
       `CompanyLogo`.`mime_type` AS `CompanyLogo__mime_type`,
       `CompanyLogo`.`extension` AS `CompanyLogo__extension`,
       `CompanyLogo`.`hash` AS `CompanyLogo__hash`,
       `CompanyLogo`.`path` AS `CompanyLogo__path`,
       `CompanyLogo`.`adapter` AS `CompanyLogo__adapter`,
       `CompanyLogo`.`created` AS `CompanyLogo__created`,
       `CompanyLogo`.`modified` AS `CompanyLogo__modified`,

       `Profiles`.`id` AS `Profiles__id`,
       `Profiles`.`profile_category_id` AS `Profiles__profile_category_id`,
       `Profiles`.`status` AS `Profiles__status`,
       `Profiles`.`company_id` AS `Profiles__company_id`,
       `Profiles`.`profile_title` AS `Profiles__profile_title`,
       `Profiles`.`logo` AS `Profiles__logo`,
       `Profiles`.`established` AS `Profiles__established`,
       `Profiles`.`employee_number` AS `Profiles__employee_number`,
       `Profiles`.`slug` AS `Profiles__slug`,
       `Profiles`.`old_slug` AS `Profiles__old_slug`,
       `Profiles`.`domain_id` AS `Profiles__domain_id`,
       `Profiles`.`language_id` AS `Profiles__language_id`,
       `Profiles`.`keywords` AS `Profiles__keywords`,
       `Profiles`.`meta_description` AS `Profiles__meta_description`,
       `Profiles`.`created` AS `Profiles__created`,
       `Profiles`.`modified` AS `Profiles__modified`,
       `Profiles`.`hits` AS `Profiles__hits`,
       `Profiles`.`like_count` AS `Profiles__like_count`,
       `Profiles`.`social_buttons` AS `Profiles__social_buttons`,
       `Profiles`.`about` AS `Profiles__about`,
       `Profiles`.`team_description` AS `Profiles__team_description`,
       `Profiles`.`award_count` AS `Profiles__award_count`,
       `Profiles`.`project_count` AS `Profiles__project_count`,
       `Profiles`.`exhibition_count` AS `Profiles__exhibition_count`,
       `Profiles`.`employee_count` AS `Profiles__employee_count`,
       `Profiles`.`publication_count` AS `Profiles__publication_count`,
       `Profiles`.`competition_count` AS `Profiles__competition_count`,
       `Profiles`.`product_count` AS `Profiles__product_count`,
       `Profiles`.`facebook` AS `Profiles__facebook`,
       `Profiles`.`twitter` AS `Profiles__twitter`,
       `Profiles`.`is_setup` AS `Profiles__is_setup`,
       `CountryStates`.`id` AS `CountryStates__id`,
       `CountryStates`.`name` AS `CountryStates__name`
FROM `wa_jobs` `Jobs`
LEFT JOIN `wa_countries` `Countries` ON `Countries`.`id` = (`Jobs`.`country_id`)
LEFT JOIN `wa_companies` `Companies` ON `Companies`.`id` = (`Jobs`.`company_id`)
LEFT JOIN `wa_file_storage` `CompanyLogo` ON (`CompanyLogo`.`model` = 'CompanyLogo' AND `Companies`.`id` = (`CompanyLogo`.`foreign_key`))
LEFT JOIN `wa_profiles` `Profiles` ON `Companies`.`id` = (`Profiles`.`company_id`)
LEFT JOIN `wa_country_states` `CountryStates` ON `CountryStates`.`id` = (`Companies`.`state_id`)
ORDER BY `Jobs`.`id` DESC
LIMIT 50
OFFSET 850
Run Code Online (Sandbox Code Playgroud)

wa_file_storage 架构:

CREATE TABLE `wa_file_storage` (
    `id` CHAR(36) NOT NULL,
    `user_id` CHAR(36) NULL DEFAULT NULL,
    `foreign_key` CHAR(36) NULL DEFAULT NULL,
    `model` VARCHAR(64) NULL DEFAULT NULL,
    `filename` VARCHAR(255) NOT NULL,
    `filesize` INT(16) NULL DEFAULT NULL,
    `mime_type` VARCHAR(32) NULL DEFAULT NULL,
    `extension` VARCHAR(5) NULL DEFAULT NULL,
    `hash` VARCHAR(64) NULL DEFAULT NULL,
    `path` VARCHAR(255) NOT NULL,
    `adapter` VARCHAR(32) NULL DEFAULT NULL COMMENT 'Gaufrette Storage Adapter Class',
    `created` DATETIME NULL DEFAULT NULL,
    `modified` DATETIME NULL DEFAULT NULL,
    PRIMARY KEY (`id`),
    INDEX `foreign_key_model` (`foreign_key`, `model`),
    INDEX `model` (`model`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
Run Code Online (Sandbox Code Playgroud)

wa_companies 架构:

CREATE TABLE `wa_companies` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `company` VARCHAR(255) NOT NULL,
    `company2` VARCHAR(255) NOT NULL,
    `company3` VARCHAR(255) NOT NULL,
    `street` VARCHAR(255) NOT NULL,
    `street2` VARCHAR(255) NOT NULL,
    `pobox` VARCHAR(255) NOT NULL,
    `postal_code` VARCHAR(255) NULL DEFAULT NULL,
    `city` VARCHAR(255) NOT NULL,
    `country_id` INT(11) NULL DEFAULT NULL,
    `state_id` INT(11) NULL DEFAULT NULL,
    `tel1` VARCHAR(255) NOT NULL,
    `tel2` VARCHAR(255) NOT NULL,
    `mobile` VARCHAR(255) NOT NULL,
    `fax` VARCHAR(255) NOT NULL,
    `email` VARCHAR(255) NOT NULL,
    `url` VARCHAR(255) NOT NULL,
    `url_text` VARCHAR(255) NOT NULL,
    `url2` VARCHAR(255) NOT NULL,
    `url2_text` VARCHAR(255) NOT NULL,
    `headoffice_id` INT(11) NULL DEFAULT NULL,
    `selection_ranking` VARCHAR(255) NOT NULL,
    `positioning` VARCHAR(255) NOT NULL,
    `advisor_id` INT(11) NOT NULL,
    `status_id` INT(11) NOT NULL DEFAULT '1',
    `client_status_id` INT(11) NULL DEFAULT '17',
    `invoice_language_id` INT(11) NOT NULL DEFAULT '5',
    `total_credits` INT(11) NOT NULL,
    `tags` VARCHAR(255) NOT NULL,
    `uses_as_own_homepage` INT(11) NOT NULL DEFAULT '0',
    `address_checked` INT(11) NOT NULL DEFAULT '0',
    `approved` TINYINT(1) NOT NULL DEFAULT '0',
    `created` DATETIME NOT NULL,
    `modified` DATETIME NOT NULL,
    `role` VARCHAR(64) NOT NULL DEFAULT 'basic',
    `logo_image_id` VARCHAR(36) NULL DEFAULT NULL,
    `branch_count` INT(8) NOT NULL DEFAULT '0',
    `profile_constraint_id` INT(11) NULL DEFAULT NULL,
    PRIMARY KEY (`id`),
    INDEX `country_id` (`country_id`),
    INDEX `state_id` (`state_id`),
    INDEX `headoffice_id` (`headoffice_id`),
    INDEX `status_id` (`status_id`),
    INDEX `client_status_id` (`client_status_id`),
    INDEX `PROFILE_CONSTRAINT_INDEX` (`profile_constraint_id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=78372;
Run Code Online (Sandbox Code Playgroud)

在 IRC 上,我被告知要显示以下结果SHOW VARIABLES LIKE 'char%';

在此输入图像描述

主要原因CHAR(36)是我们在较新的表上使用 UUID。这个数据库已有 12 年以上的历史,涉及约 250 个表,我是来解决这个混乱的问题的。:) 我不介意添加另一个表,但主要思想是该表充当所有文件的参考点,无论它们存储在何处。

Wil*_*ema 5

事实上,您有 wa_companies.id 的 INT 和 wa_file_storage.foreign_key 的 CHAR(36) ,这是问题的一部分。MySQL 必须在这些数据类型之间进行转换才能进行比较,这将从根本上减慢连接速度。

虽然显示的值对于人类来说看起来是相同的,但对于计算机来说,这些值并不相同,因此在比较之前必须进行转换。进行此比较所需的时间很短,但当您考虑到它必须基本上转换每一行以验证其是否匹配时,时间会增加得非常快。(确切的技术细节可能不准确,但就性能变化而言,总体要点和直觉是正确的。)

构建表的最佳方法是不要使用单个“大型”表,而是创建具有最小大小列的单独的多对多表,以便有效地联接。

此外,通过创建多对多表,您可以通过 wa_file_storage.id 键从该表连接到 wa_file_storage 表。这将使您能够访问聚集索引(也称为主键,至少对于 MySQL InnoDB 表)中存在的所有 SELECTed 列。如果 MySQL 最终使用索引,然后 SELECT 列不在索引中,则最终必须对聚集索引进行第二次查找,才能获取这些值。

请记住,JOIN 并不慢。构造不良的 JOIN 会使 RDBMS 执行大量工作,但速度很慢。保持键较小且数据类型一致,并尽可能连接到主键(或覆盖索引),以使 RDBMS 保持良好和简单的状态。