You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MariaDB 10.7查询因索引选择过慢,如何指定最优索引?

问题描述

有一条查询语句在MariaDB 10.3中执行速度很快,但迁移到MariaDB 10.7后,执行耗时长达6分钟。

查询语句

SELECT
    products.code AS productCode,
    products.`name` AS productDescription,
    products.unit_of_measure,
    product_types.fg_or_rp AS productType,
    product_batches.is_blocked,
    CASE WHEN product_batches.expiry_date < NOW() AND products.is_batch_tracked = 1 THEN
        1
    ELSE
        0
    END AS is_expired,
    sum(pallet_items.quantity) AS quantity_soh
FROM
    products
    INNER JOIN product_batches ON product_batches.product_id = products.id
    INNER JOIN pallet_items ON pallet_items.product_batch_id = product_batches.id
    INNER JOIN pallets ON pallets.id = pallet_items.pallet_id
    INNER JOIN storage_locations ON storage_locations.id = pallets.current_location_id
    INNER JOIN product_types ON products.product_type_id = product_types.id
    INNER JOIN stock_locations ON stock_locations.id = storage_locations.stock_location_id
WHERE
    stock_locations.stock_group_id in(
        SELECT
            id FROM stock_groups
        WHERE
            stock_groups.include_in_stock_on_hand = 1)
GROUP BY
    products.code, products.`name`, unit_of_measure, product_batches.is_blocked,
    CASE WHEN product_batches.expiry_date < NOW() AND products.is_batch_tracked = 1 THEN
        1
    ELSE
        0
    END, product_types.fg_or_rp, product_batches.is_blocked
ORDER BY
    products.code

索引选择差异

通过EXPLAIN分析发现,两个版本在pallet_items表的索引选择上存在明显差异:

  • MariaDB 10.3(执行快速)使用索引:pallet_items_pallet_id_foreign
  • MariaDB 10.7(执行缓慢)使用索引:pallet_items_product_batch_id_foreign

相关表结构

pallet_types

CREATE TABLE `pallet_types` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `description` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

storage_locations

CREATE TABLE `storage_locations` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `description` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `entry_x` int(11) DEFAULT NULL,
  `entry_y` int(11) DEFAULT NULL,
  `entry_z` int(11) DEFAULT NULL,
  `exit_x` int(11) DEFAULT NULL,
  `exit_y` int(11) DEFAULT NULL,
  `exit_z` int(11) DEFAULT NULL,
  `status` int(11) DEFAULT NULL,
  `storage_function_id` bigint(20) unsigned DEFAULT 1,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  `max_quantity` int(10) unsigned DEFAULT NULL,
  `is_multi_product` tinyint(1) DEFAULT 0,
  `stock_location_id` bigint(20) unsigned DEFAULT 2,
  `client_location_code` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `storage_locations_storage_function_id_foreign` (`storage_function_id`),
  KEY `storage_locations_stock_location_id_foreign` (`stock_location_id`),
  CONSTRAINT `storage_locations_stock_location_id_foreign` FOREIGN KEY (`stock_location_id`) REFERENCES `stock_locations` (`id`),
  CONSTRAINT `storage_locations_storage_function_id_foreign` FOREIGN KEY (`storage_function_id`) REFERENCES `storage_functions` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=770 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

stock_locations

CREATE TABLE `stock_locations` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `stock_group_id` bigint(20) unsigned NOT NULL,
  `description` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `stock_locations_stock_group_id_foreign` (`stock_group_id`),
  CONSTRAINT `stock_locations_stock_group_id_foreign` FOREIGN KEY (`stock_group_id`) REFERENCES `stock_groups` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

stock_groups

CREATE TABLE `stock_groups` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `description` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `include_in_stock_on_hand` tinyint(1) NOT NULL DEFAULT 1,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

pallet_statuses

CREATE TABLE `pallet_statuses` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `description` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

pallet_ledger_entries

CREATE TABLE `pallet_ledger_entries` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `pallet_id` bigint(20) unsigned NOT NULL,
  `product_id` bigint(20) unsigned NOT NULL,
  `product_batch_id` bigint(20) unsigned DEFAULT NULL,
  `quantity` decimal(14,4) NOT NULL,
  `event_type` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  `reference` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `soh_pallet` decimal(14,4) NOT NULL,
  `user_id` bigint(20) unsigned DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `pallet_ledger_entries_product_id_foreign` (`product_id`),
  KEY `pallet_ledger_entries_product_batch_id_foreign` (`product_batch_id`),
  KEY `pallet_ledger_entries_pallet_id_foreign` (`pallet_id`),
  KEY `pallet_ledger_entries_user_id_foreign` (`user_id`),
  CONSTRAINT `pallet_ledger_entries_pallet_id_foreign` FOREIGN KEY (`pallet_id`) REFERENCES `pallets` (`id`),
  CONSTRAINT `pallet_ledger_entries_product_batch_id_foreign` FOREIGN KEY (`product_batch_id`) REFERENCES `product_batches` (`id`),
  CONSTRAINT `pallet_ledger_entries_product_id_foreign` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`),
  CONSTRAINT `pallet_ledger_entries_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=231839 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

products

CREATE TABLE `products` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `product_type_id` bigint(20) unsigned DEFAULT NULL,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `code` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `is_batch_tracked` tinyint(1) NOT NULL DEFAULT 1,
  `metric_of_weight` decimal(10,3) DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  `unit_of_measure` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `cost` decimal(20,10) DEFAULT NULL,
  `unit_ean` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `shrink_ean` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `case_ean` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `shipping_weight` decimal(10,2) DEFAULT NULL,
  `shipping_weight_unit` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `net_weight` decimal(10,2) DEFAULT NULL,
  `net_weight_unit` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `size` decimal(10,2) DEFAULT NULL,
  `size_unit` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `production_line` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `shelf_life_days` int(11) DEFAULT NULL,
  `exclude_from_receiving_stack` tinyint(1) NOT NULL DEFAULT 0,
  `ignore_batch_no_check` tinyint(1) NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  KEY `products_product_type_id_foreign` (`product_type_id`),
  CONSTRAINT `products_product_type_id_foreign` FOREIGN KEY (`product_type_id`) REFERENCES `product_types` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=738 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

product_types

CREATE TABLE `product_types` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `description` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `fg_or_rp` varchar(2) COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
解决方案

要让MariaDB 10.7选择更优的索引,可尝试以下几种方法:

1. 强制指定索引

直接在查询中通过FORCE INDEX强制数据库使用pallet_items_pallet_id_foreign索引,这是最直接的临时解决方案:

SELECT
    products.code AS productCode,
    products.`name` AS productDescription,
    products.unit_of_measure,
    product_types.fg_or_rp AS productType,
    product_batches.is_blocked,
    CASE WHEN product_batches.expiry_date < NOW() AND products.is_batch_tracked = 1 THEN
        1
    ELSE
        0
    END AS is_expired,
    sum(pallet_items.quantity) AS quantity_soh
FROM
    products
    INNER JOIN product_batches ON product_batches.product_id = products.id
    INNER JOIN pallet_items FORCE INDEX (pallet_items_pallet_id_foreign) ON pallet_items.product_batch_id = product_batches.id
    INNER JOIN pallets ON pallets.id = pallet_items.pallet_id
    INNER JOIN storage_locations ON storage_locations.id = pallets.current_location_id
    INNER JOIN product_types ON products.product_type_id = product_types.id
    INNER JOIN stock_locations ON stock_locations.id = storage_locations.stock_location_id
WHERE
    stock_locations.stock_group_id in(
        SELECT
            id FROM stock_groups
        WHERE
            stock_groups.include_in_stock_on_hand = 1)
GROUP BY
    products.code, products.`name`, unit_of_measure, product_batches.is_blocked,
    CASE WHEN product_batches.expiry_date < NOW() AND products.is_batch_tracked = 1 THEN
        1
    ELSE
        0
    END, product_types.fg_or_rp, product_batches.is_blocked
ORDER BY
    products.code

2. 更新表统计信息

MariaDB 10.7的查询优化器依赖最新的表统计信息来评估索引成本,升级后统计信息可能未同步导致选择错误索引。执行以下命令更新相关表的统计信息:

ANALYZE TABLE pallet_items, pallets, storage_locations, stock_locations, stock_groups, products, product_batches, product_types;

3. 创建复合索引

针对查询逻辑创建更高效的复合索引,减少回表操作。例如在pallet_items表上创建包含关联字段和聚合字段的索引:

CREATE INDEX idx_pallet_items_pallet_batch_qty ON pallet_items (pallet_id, product_batch_id, quantity);

该索引覆盖了关联所需的pallet_id、product_batch_id以及聚合用的quantity,能直接从索引中获取数据,提升查询效率。

4. 重构查询逻辑

将子查询改为JOIN操作,帮助优化器更好地理解数据关联关系,从而选择更优的执行计划:

SELECT
    products.code AS productCode,
    products.`name` AS productDescription,
    products.unit_of_measure,
    product_types.fg_or_rp AS productType,
    product_batches.is_blocked,
    CASE WHEN product_batches.expiry_date < NOW() AND products.is_batch_tracked = 1 THEN
        1
    ELSE
        0
    END AS is_expired,
    sum(pallet_items.quantity) AS quantity_soh
FROM
    products
    INNER JOIN product_batches ON product_batches.product_id = products.id
    INNER JOIN pallet_items ON pallet_items.product_batch_id = product_batches.id
    INNER JOIN pallets ON pallets.id = pallet_items.pallet_id
    INNER JOIN storage_locations ON storage_locations.id = pallets.current_location_id
    INNER JOIN stock_locations ON stock_locations.id = storage_locations.stock_location_id
    INNER JOIN stock_groups ON stock_groups.id = stock_locations.stock_group_id AND stock_groups.include_in_stock_on_hand = 1
    INNER JOIN product_types ON products.product_type_id = product_types.id
GROUP BY
    products.code, products.`name`, unit_of_measure, product_batches.is_blocked,
    CASE WHEN product_batches.expiry_date < NOW() AND products.is_batch_tracked = 1 THEN
        1
    ELSE
        0
    END, product_types.fg_or_rp, product_batches.is_blocked
ORDER BY
    products.code

5. 调整优化器参数

临时调整优化器参数,强制其选择更优的执行路径。例如关闭基于成本的MRR(Multi-Range Read)优化:

SET SESSION optimizer_switch='mrr=off,mrr_cost_based=off';

测试有效后,可考虑在my.cnf或my.ini中持久化该设置,但需注意全局调整可能影响其他查询。

内容的提问来源于stack exchange,提问作者Iain

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 20:19:01