【问题标题】:MySQL Join 1 to 1 in N items tableMySQL Join 1 to 1 in N items table
【发布时间】:2017-02-26 17:53:08
【问题描述】:

我需要一些关于我的商店数据库结构的帮助。要从类别中获取项目,已经有一些连接,但我想从其他表中将图像添加到项目中。主项目表没有图像 id,所以我们只是从其他表中获取一个,通过它的权重 = 1。

所以,有一个结构:

CREATE TABLE IF NOT EXISTS `categories` (
  `category_id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name`        VARCHAR(255)
                CHARACTER SET utf8mb4     DEFAULT NULL
  COMMENT 'Category title',
  `parent_id`   INT(11)          NOT NULL DEFAULT '0'
  COMMENT 'Category Parent ID',
  `status`      TINYINT(1)                DEFAULT '0'
  COMMENT 'Category active status',
  `weight`      INT(11)                   DEFAULT '0',
  `slug`        VARCHAR(255)
                CHARACTER SET utf8mb4     DEFAULT NULL
  COMMENT 'Category url alias',
  PRIMARY KEY (`category_id`)
)
  ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS `items` (
  `item_id`     INT(11) UNSIGNED           NOT NULL AUTO_INCREMENT,
  `name`        VARCHAR(255)
                CHARACTER SET utf8mb4      NOT NULL DEFAULT ''
  COMMENT 'Item name',
  `description` TEXT CHARACTER SET utf8mb4 NOT NULL
  COMMENT 'Item description',
  `user_id`     INT(11) UNSIGNED           NOT NULL DEFAULT '0'
  COMMENT 'User id',
  `category_id` INT(11) UNSIGNED           NOT NULL DEFAULT '0'
  COMMENT 'Category id',
  `price`       DECIMAL(10, 2)             NOT NULL DEFAULT '0.00'
  COMMENT 'Item price',
  `status`      INT(1) UNSIGNED            NOT NULL DEFAULT '1'
  COMMENT 'Item status',
  `deleted`     INT(1) UNSIGNED            NOT NULL DEFAULT '0'
  COMMENT 'Delite status',
  `blocked`     INT(1) UNSIGNED            NOT NULL DEFAULT '0'
  COMMENT 'Block status',
  PRIMARY KEY (`item_id`)
)
  ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS `items_images` (
  `id`      INT(11) UNSIGNED      NOT NULL AUTO_INCREMENT,
  `item_id` INT(10) UNSIGNED      NOT NULL DEFAULT '0',
  `file`    VARCHAR(255)
            CHARACTER SET utf8mb4 NOT NULL DEFAULT '',
  `weight`  INT(11) UNSIGNED      NOT NULL DEFAULT '0',
  `status`  TINYINT(1) UNSIGNED   NOT NULL DEFAULT '0',
  `deleted` TINYINT(1) UNSIGNED   NOT NULL DEFAULT '0',
  `created` INT(11) UNSIGNED      NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
)
  ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci
  ROW_FORMAT = COMPACT;

从那里可以看到,类别结构内部也包含parent_id,所以我们总能得到类别树。

使用此查询效果很好,而且速度非常快。

SELECT
  `i`.*,
  `c1`.`name` AS `category_name`,
  `c1`.`slug` AS `category_slug`,
  `c2`.`name` AS `subcategory_name`,
  `c2`.`slug` AS `subcategory_slug`
FROM `items` AS `i`
  LEFT JOIN `categories` AS `c2` ON `c2`.`category_id` = `i`.`category_id`
  LEFT JOIN `categories` AS `c1` ON `c1`.`category_id` = `c2`.`parent_id`
WHERE `i`.`deleted` = 0 AND `i`.`blocked` = 0 AND `i`.`status` = 1
ORDER BY `i`.`created` DESC
LIMIT 40

但是,如果我加入 items_images 喜欢:

SELECT
  `i`.*,
  `ii`.`file` AS `image`,
  `c1`.`name` AS `category_name`,
  `c1`.`slug` AS `category_slug`,
  `c2`.`name` AS `subcategory_name`,
  `c2`.`slug` AS `subcategory_slug`
FROM `items` AS `i`
  LEFT JOIN `items_images` AS `ii`
    ON `i`.`item_id` = `ii`.`item_id` AND `ii`.`weight` = 1
   AND `ii`.`status` = 1 AND `ii`.`deleted` = 0
  LEFT JOIN `categories` AS `c2` ON `c2`.`category_id` = `i`.`category_id`
  LEFT JOIN `categories` AS `c1` ON `c1`.`category_id` = `c2`.`parent_id`
WHERE `i`.`deleted` = 0 AND `i`.`blocked` = 0 AND `i`.`status` = 1
 ORDER BY `i`.`created` DESC LIMIT 40

有时,14k 个项目和 40k 个图像最多需要 1 分钟。

有什么可以改进的吗?!请注意,也可能有没有图像的项目。这不是必需的。

少量添加。即使是单个图像加入项目也会使查询运行超过一分钟。这是一个示例:

SELECT `i`.*, `ii`.`file` AS `image` FROM `items` AS `i` 
LEFT JOIN `items_images` AS `ii` ON `i`.`item_id` = `ii`.`item_id`
         AND `ii`.`weight` = 1 AND `ii`.`status` = 1 AND `ii`.`deleted` = 0 
WHERE `i`.`deleted` =0 AND `i`.`sold` =0 AND `i`.`blocked` =0 AND `i`.`status` = 1 
ORDER BY `i`.`created` DESC

【问题讨论】:

  • 请注意,没有 ORDER BY 的 LIMIT 是一个相当没有意义的概念 - 并不是说​​这对性能有任何帮助。我们可以看到问题查询的 EXPLAIN 吗?
  • @Strawberry 我添加了订单和限制。还是什么都没有。
  • 如我所说。请提供解释
  • 你可能有一个规则,每列都有一个“评论”,但这很可笑:description ... COMMENT 'Item description'

标签: mysql performance join


【解决方案1】:
  1. 您的命名约定不一致。
  2. 索引 parent_id

    ALTER TABLE 类别 ADD INDEX parent_id_ind (parent_id ASC) ;

  3. 尝试再次运行您的查询。

在数据库中,您可以使用索引来提高数据检索的速度。索引通常在 JOIN、WHERE 和 ORDER BY 子句中使用的列上创建。

【讨论】:

  • 添加了索引,还是一样。加入图像需要很长时间。
  • 索引前后多少时间?
  • 还有一分钟多...即使在项目中加入图像也会杀死一切。示例:
  • @GordonFreeman Index items_images.item_id also, ALTER TABLE items_images ADD INDEX item_id_ind (item_id ASC) ;
  • OP 修正了问题,添加了事实上在没有类别的情况下问题仍然存在。因此,在类别上添加索引不可能解决它。另一方面,items_imagesitem_id 的索引...
【解决方案2】:

我建议你改进你的结构,你可以接受或离开它,

这是我的建议:

    DROP TABLE IF EXISTS `category`;
  CREATE TABLE `category` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT 'Category title',
  `category_id` int(11) NOT NULL DEFAULT '0' COMMENT 'Category Parent ID',
  `status` enum('status1','status2') COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'Category active status',
  `weight` int(11) DEFAULT '0',
  `slug` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT 'Category url alias',
  PRIMARY KEY (`id`),
  KEY `category_id_ind` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

    DROP TABLE IF EXISTS `item`;
    CREATE TABLE `item` (
      `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
      `name` varchar(255) CHARACTER SET utf8mb4 NOT NULL DEFAULT '' COMMENT 'Item name',
      `description` text CHARACTER SET utf8mb4 NOT NULL COMMENT 'Item description',
      `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT 'User id',
      `category_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT 'Category id',
      `price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT 'Item price',
      `status` enum('status1','status2') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'status1' COMMENT 'Item status',
      `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT 'Delite status',
      `is_blocked` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT 'Block status',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

    DROP TABLE IF EXISTS `item_images`;
    CREATE TABLE `item_images` (
      `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
      `item_id` int(10) unsigned NOT NULL DEFAULT '0',
      `file_path` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL ,
      `weight` int(11) unsigned NOT NULL DEFAULT '0',
      `status` tinyint(1) unsigned NOT NULL DEFAULT '0',
      `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
      `creation_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPACT;

【讨论】:

  • 重命名列并不会提高速度。
  • @4EACH -- 请提供所做更改的概要。
【解决方案3】:

添加复合索引:

items:  INDEX(deleted, blocked, status, created)  -- created must be last
items_images:  INDEX(item_id, weight, status, deleted)
categories:  INDEX(category_id, deleted, blocked, status)

除非另有说明,列的顺序并不重要。

删除LEFT,除非您认为有必要。

请提供EXPLAIN SELECT ...

缓慢的可能原因是必须检查大量图像行以查找weight=1。最重要的是,缺少任何有用的索引意味着昂贵的表扫描。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-16
    • 2013-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-06
    • 2022-12-01
    • 2022-12-19
    相关资源
    最近更新 更多