数据库监控与调优【十九】—— SQL调优实战

SQL调优实战

SQL调优实战1-常规调优

项目建表语句
-- ----------------------------
-- Table structure for carousel
-- ----------------------------
DROP TABLE IF EXISTS `carousel`;
CREATE TABLE `carousel`  (
  `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '主键',
  `image_url` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '图片 图片地址',
  `background_color` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '背景色',
  `item_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '商品id 商品id',
  `cat_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '商品分类id 商品分类id',
  `type` int(0) NOT NULL COMMENT '轮播图类型 轮播图类型,用于判断,可以根据商品id或者分类进行页面跳转,1:商品 2:分类',
  `sort` int(0) NOT NULL COMMENT '轮播图展示顺序',
  `is_show` int(0) NOT NULL COMMENT '是否展示',
  `create_time` datetime(0) NOT NULL COMMENT '创建时间 创建时间',
  `update_time` datetime(0) NOT NULL COMMENT '更新时间 更新',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '轮播图 ' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for category
-- ----------------------------
DROP TABLE IF EXISTS `category`;
CREATE TABLE `category`  (
  `id` int(0) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '分类名称',
  `type` int(0) NOT NULL COMMENT '分类类型',
  `father_id` int(0) NOT NULL COMMENT '父id',
  `logo` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '图标',
  `slogan` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '口号',
  `cat_image` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '分类图',
  `bg_color` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '背景颜色',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 220073 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '商品分类 ' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for items
-- ----------------------------
DROP TABLE IF EXISTS `items`;
CREATE TABLE `items`  (
  `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品主键id',
  `item_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品名称 商品名称',
  `cat_id` int(0) NOT NULL COMMENT '分类外键id 分类id',
  `root_cat_id` int(0) NOT NULL COMMENT '一级分类外键id',
  `sell_counts` int(0) NOT NULL COMMENT '累计销售 累计销售',
  `on_off_status` int(0) NOT NULL COMMENT '上下架状态 上下架状态,1:上架 2:下架',
  `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品内容 商品内容',
  `created_time` datetime(0) NOT NULL COMMENT '创建时间',
  `updated_time` datetime(0) NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `items_sell_counts_item_name_index`(`sell_counts`, `item_name`) USING BTREE,
  INDEX `items_item_name_sell_counts_index`(`item_name`, `sell_counts`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '商品表 商品信息相关表:分类表,商品图片表,商品规格表,商品参数表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for items_comments
-- ----------------------------
DROP TABLE IF EXISTS `items_comments`;
CREATE TABLE `items_comments`  (
  `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'id主键',
  `user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户id 用户名须脱敏',
  `item_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品id',
  `item_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '商品名称',
  `item_spec_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '商品规格id 可为空',
  `sepc_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '规格名称 可为空',
  `comment_level` int(0) NOT NULL COMMENT '评价等级 1:好评 2:中评 3:差评',
  `content` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '评价内容',
  `created_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `updated_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '商品评价表 ' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for items_img
-- ----------------------------
DROP TABLE IF EXISTS `items_img`;
CREATE TABLE `items_img`  (
  `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '图片主键',
  `item_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品外键id 商品外键id',
  `url` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '图片地址 图片地址',
  `sort` int(0) NOT NULL COMMENT '顺序 图片顺序,从小到大',
  `is_main` int(0) NOT NULL COMMENT '是否主图 是否主图,1:是,0:否',
  `created_time` datetime(0) NOT NULL COMMENT '创建时间',
  `updated_time` datetime(0) NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `items_img_is_main_item_id_index`(`is_main`, `item_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '商品图片 ' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for items_param
-- ----------------------------
DROP TABLE IF EXISTS `items_param`;
CREATE TABLE `items_param`  (
  `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品参数id',
  `item_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品外键id',
  `produc_place` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '产地 产地,例:中国江苏',
  `foot_period` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '保质期 保质期,例:180天',
  `brand` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '品牌名 品牌名,例:三只大灰狼',
  `factory_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '生产厂名 生产厂名,例:大灰狼工厂',
  `factory_address` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '生产厂址 生产厂址,例:大灰狼生产基地',
  `packaging_method` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '包装方式 包装方式,例:袋装',
  `weight` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '规格重量 规格重量,例:35g',
  `storage_method` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '存储方法 存储方法,例:常温5~25°',
  `eat_method` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '食用方式 食用方式,例:开袋即食',
  `created_time` datetime(0) NOT NULL COMMENT '创建时间',
  `updated_time` datetime(0) NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '商品参数 ' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for items_spec
-- ----------------------------
DROP TABLE IF EXISTS `items_spec`;
CREATE TABLE `items_spec`  (
  `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品规格id',
  `item_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品外键id',
  `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '规格名称',
  `stock` int(0) NOT NULL COMMENT '库存',
  `discounts` decimal(4, 2) NOT NULL COMMENT '折扣力度',
  `price_discount` int(0) NOT NULL COMMENT '优惠价',
  `price_normal` int(0) NOT NULL COMMENT '原价',
  `created_time` datetime(0) NOT NULL COMMENT '创建时间',
  `updated_time` datetime(0) NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `price_discount_item_id_price_discount_index`(`item_id`, `price_discount`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '商品规格 每一件商品都有不同的规格,不同的规格又有不同的价格和优惠力度,规格表为此设计' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for order_items
-- ----------------------------
DROP TABLE IF EXISTS `order_items`;
CREATE TABLE `order_items`  (
  `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '主键id',
  `order_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '归属订单id',
  `item_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品id',
  `item_img` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品图片',
  `item_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品名称',
  `item_spec_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '规格id',
  `item_spec_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '规格名称',
  `price` int(0) NOT NULL COMMENT '成交价格',
  `buy_counts` int(0) NOT NULL COMMENT '购买数量',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '订单商品关联表 ' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for order_status
-- ----------------------------
DROP TABLE IF EXISTS `order_status`;
CREATE TABLE `order_status`  (
  `order_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '订单ID;对应订单表的主键id',
  `order_status` int(0) NOT NULL COMMENT '订单状态',
  `created_time` datetime(0) NULL DEFAULT NULL COMMENT '订单创建时间;对应[10:待付款]状态',
  `pay_time` datetime(0) NULL DEFAULT NULL COMMENT '支付成功时间;对应[20:已付款,待发货]状态',
  `deliver_time` datetime(0) NULL DEFAULT NULL COMMENT '发货时间;对应[30:已发货,待收货]状态',
  `success_time` datetime(0) NULL DEFAULT NULL COMMENT '交易成功时间;对应[40:交易成功]状态',
  `close_time` datetime(0) NULL DEFAULT NULL COMMENT '交易关闭时间;对应[50:交易关闭]状态',
  `comment_time` datetime(0) NULL DEFAULT NULL COMMENT '留言时间;用户在交易成功后的留言时间',
  PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '订单状态表;订单的每个状态更改都需要进行记录\n10:待付款  20:已付款,待发货  30:已发货,待收货(7天自动确认)  40:交易成功(此时可以评价)50:交易关闭(待付款时,用户取消 或 长时间未付款,系统识别后自动关闭)\n退货/退货,此分支流程不做,所以不加入' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for orders
-- ----------------------------
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders`  (
  `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '订单主键;同时也是订单编号',
  `user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户id',
  `receiver_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '收货人快照',
  `receiver_mobile` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '收货人手机号快照',
  `receiver_address` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '收货地址快照',
  `total_amount` int(0) NOT NULL COMMENT '订单总价格',
  `real_pay_amount` int(0) NOT NULL COMMENT '实际支付总价格',
  `post_amount` int(0) NOT NULL COMMENT '邮费;默认可以为零,代表包邮',
  `pay_method` int(0) NOT NULL COMMENT '支付方式',
  `left_msg` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '买家留言',
  `extand` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '扩展字段',
  `is_comment` int(0) NOT NULL COMMENT '买家是否评价;1:已评价,0:未评价',
  `is_delete` int(0) NOT NULL COMMENT '逻辑删除状态;1: 删除 0:未删除',
  `created_time` datetime(0) NOT NULL COMMENT '创建时间(成交时间)',
  `updated_time` datetime(0) NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '订单表;' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for stu
-- ----------------------------
DROP TABLE IF EXISTS `stu`;
CREATE TABLE `stu`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `age` int(0) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7347 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for user_address
-- ----------------------------
DROP TABLE IF EXISTS `user_address`;
CREATE TABLE `user_address`  (
  `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '地址主键id',
  `user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '关联用户id',
  `receiver` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '收件人姓名',
  `mobile` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '收件人手机号',
  `province` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '省份',
  `city` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '城市',
  `district` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '区县',
  `detail` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '详细地址',
  `extand` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '扩展字段',
  `is_default` int(0) NULL DEFAULT NULL COMMENT '是否默认地址',
  `created_time` datetime(0) NOT NULL COMMENT '创建时间',
  `updated_time` datetime(0) NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户地址表 ' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users`  (
  `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键id 用户id',
  `username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名 用户名',
  `password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码 密码',
  `nickname` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称 昵称',
  `realname` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '真实姓名',
  `face` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '头像',
  `mobile` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号 手机号',
  `email` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱地址 邮箱地址',
  `sex` int(0) NULL DEFAULT NULL COMMENT '性别 性别 1:男  0:女  2:保密',
  `birthday` date NULL DEFAULT NULL COMMENT '生日 生日',
  `created_time` datetime(0) NOT NULL COMMENT '创建时间 创建时间',
  `updated_time` datetime(0) NOT NULL COMMENT '更新时间 更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表 ' ROW_FORMAT = Dynamic;
查看项目数据量
-- 174
select count(*) from items;

-- 350
select count(*) from items_img;

数据量太少,很难体现出调优后的性能差异

创建存储过程
-- 关闭MySQL对存储过程的限制
set global log_bin_trust_function_creators = 0;

-- 执行insert into select...,重复repeat_times次
drop procedure if exists prepare_test_data;
DELIMITER $$
CREATE PROCEDURE prepare_test_data(IN repeat_times INT(10))
BEGIN
    DECLARE i INT DEFAULT 0;
    loopname:
    LOOP
        SET i = i + 1;

        INSERT INTO carousel (id, image_url, background_color, item_id, cat_id, type, sort, is_show,
                              create_time, update_time)
        SELECT uuid(),
               image_url,
               background_color,
               item_id,
               cat_id,
               type,
               sort,
               is_show,
               create_time,
               update_time
        from carousel;

        insert into category (name, type, father_id, logo, slogan, cat_image, bg_color)
        select name, type, father_id, logo, slogan, cat_image, bg_color
        from category;

        insert into items(id, item_name, cat_id, root_cat_id, sell_counts, on_off_status, content,
                          created_time, updated_time)
        select uuid(),
               item_name,
               cat_id,
               root_cat_id,
               sell_counts,
               on_off_status,
               content,
               created_time,
               updated_time
        from items;

        insert into items_comments (id, user_id, item_id, item_name, item_spec_id, sepc_name, comment_level,
                                    content, created_time, updated_time)
        select uuid(),
               user_id,
               item_id,
               item_name,
               item_spec_id,
               sepc_name,
               comment_level,
               content,
               created_time,
               updated_time
        from items_comments;

        insert into items_img(id, item_id, url, sort, is_main, created_time, updated_time)
        select uuid(), item_id, url, sort, is_main, created_time, updated_time
        from items_img;

        insert into items_param(id, item_id, produc_place, foot_period, brand, factory_name,
                                factory_address, packaging_method, weight, storage_method, eat_method,
                                created_time, updated_time)
        select uuid(),
               item_id,
               produc_place,
               foot_period,
               brand,
               factory_name,
               factory_address,
               packaging_method,
               weight,
               storage_method,
               eat_method,
               created_time,
               updated_time
        from items_param;

        insert into items_spec (id, item_id, name, stock, discounts, price_discount, price_normal,
                                created_time, updated_time)
        select uuid(),
               item_id,
               name,
               stock,
               discounts,
               price_discount,
               price_normal,
               created_time,
               updated_time
        from items_spec;

        insert into order_items (id, order_id, item_id, item_img, item_name, item_spec_id, item_spec_name,
                                 price, buy_counts)
        select uuid(),
               order_id,
               item_id,
               item_img,
               item_name,
               item_spec_id,
               item_spec_name,
               price,
               buy_counts
        from order_items;

        insert into order_status (order_id, order_status, created_time, pay_time, deliver_time,
                                  success_time, close_time, comment_time)
        select uuid(),
               order_status,
               created_time,
               pay_time,
               deliver_time,
               success_time,
               close_time,
               comment_time
        from order_status;

        insert into orders (id, user_id, receiver_name, receiver_mobile, receiver_address, total_amount,
                            real_pay_amount, post_amount, pay_method, left_msg, extand, is_comment,
                            is_delete, created_time, updated_time)
        select uuid(),
               user_id,
               receiver_name,
               receiver_mobile,
               receiver_address,
               total_amount,
               real_pay_amount,
               post_amount,
               pay_method,
               left_msg,
               extand,
               is_comment,
               is_delete,
               created_time,
               updated_time
        from orders;

        insert into stu (name, age)
        select name, age
        from stu;

        insert into user_address (id, user_id, receiver, mobile, province, city, district, detail, extand,
                                  is_default, created_time, updated_time)
        select uuid(),
               user_id,
               receiver,
               mobile,
               province,
               city,
               district,
               detail,
               extand,
               is_default,
               created_time,
               updated_time
        from user_address;

        insert into users(id, username, password, nickname, realname, face, mobile, email, sex, birthday,
                          created_time, updated_time)
        select uuid(),
               username,
               password,
               nickname,
               realname,
               face,
               mobile,
               email,
               sex,
               birthday,
               created_time,
               updated_time
        from users;

        IF i = repeat_times THEN
            LEAVE loopname;
        END IF;
    END LOOP loopname;
END $$;
使用存储过程生成数据
-- 把foodie-dev项目里面所有的表数据量变成原先的2^10倍
call prepare_test_data(10);
查看项目现在的数据量
-- 178176
select count(*) from items;

-- 358400
select count(*) from items_img;
设置慢查询日志记录慢SQL大小
## 查看执行时间超过这么久才记录到慢查询日志,单位秒,可使用小数表示小于秒的时间
show variables like '%long_query_time%';

## 设置执行时间超过这么久才记录到慢查询日志,单位秒,可使用小数表示小于秒的时间
set long_query_time = 0.2;
避免干扰清空日志文件
## 进入目录
cd /var/lib/mysql

## 清空文件(> 文件名)
> es23-slow.log

## 查看文件大小
ls -lh
启动项目调用搜索商品列表接口
GET http://localhost:8088/items/search?keywords=好吃蛋糕甜点蒸蛋糕&sort=c&page=1&pageSize10
Accept: application/json

可以看到这个接口肉眼可见的慢,花费1.053s

查询慢查询日志该接口执行的SQL
SELECT
	i.id AS itemId,
	i.item_name AS itemName,
	i.sell_counts AS sellCounts,
	ii.url AS imgUrl,
	tempSpec.price_discount AS price 
FROM
	items i STRAIGHT_JOIN items_img ii ON i.id = ii.item_id
	LEFT JOIN ( SELECT item_id, MIN( price_discount ) AS price_discount FROM items_spec GROUP BY item_id ) tempSpec ON i.id = tempSpec.item_id 
WHERE
	ii.is_main = 1 
	AND i.item_name LIKE '%好吃蛋糕甜点蒸蛋糕%' 
ORDER BY
	i.sell_counts DESC 
	LIMIT 10;

-- 优化之前需要花费0.437s
具体优化操作

使用EXPLAIN分析

EXPLAIN SELECT
	i.id AS itemId,
	i.item_name AS itemName,
	i.sell_counts AS sellCounts,
	ii.url AS imgUrl,
	tempSpec.price_discount AS price 
FROM
	items i
	LEFT JOIN items_img ii ON i.id = ii.item_id
	LEFT JOIN ( SELECT item_id, MIN( price_discount ) AS price_discount FROM items_spec GROUP BY item_id ) tempSpec ON i.id = tempSpec.item_id 
WHERE
	ii.is_main = 1 
	AND i.item_name LIKE '%好吃蛋糕甜点蒸蛋糕%' 
ORDER BY
	i.sell_counts DESC 
	LIMIT 10;

在这里插入图片描述

优化一

由于EXPLAIN后的id存在多个值,id大的值会先执行,所以先执行这条sql

SELECT item_id, MIN( price_discount ) AS price_discount FROM items_spec GROUP BY item_id

可以看到这是一个GROUP BY语句,所以可以创建一个索引,如下

ALTER TABLE items_spec ADD INDEX price_discount_item_id_price_discount_index (item_id, price_discount);

-- 优化一之后需要花费0.231s

再次执行EXPLAIN查看

在这里插入图片描述

可以发现该条SQL的type变成range,并且Extra中显示了Using index for group-by表示使用了松散索引扫描

优化二

可以看到查询ii表的type也是ALL,使用了全表扫描,根据SQL中查询需要的字段故可以创建以下索引

ALTER TABLE items_img ADD INDEX items_img_is_main_item_id_index (is_main, item_id);

-- 优化二之后需要花费0.281s,反而增加了
-- 暂时不知道咋回事,但是这种优化方案是适用的!

再次执行EXPLAIN查看

在这里插入图片描述

可以发现操作ii表的SQL的type变成ref,此时已经没有全表扫描了

优化三

可以看到操作i表的SQL中只操作了字段item_name、sell_counts、id(已经有索引了),故可以为这两个字段创建组合索引,希望覆盖i表上面的所有字段

ALTER TABLE items ADD INDEX items_sell_counts_item_name_index (sell_counts, item_name);

但是由于是先扫描了ii表再扫描i表,而i表和ii表的关系是通过ON i.id = ii.item_id关联的,因此只会使用i表的主键

要想使用i表上面的组合索引,就需要把两张表的连接方式由LEFT JOIN改为STRAIGHT_JOIN,强制使用i表作为驱动表

再次执行EXPLAIN查看

EXPLAIN SELECT
	i.id AS itemId,
	i.item_name AS itemName,
	i.sell_counts AS sellCounts,
	ii.url AS imgUrl,
	tempSpec.price_discount AS price 
FROM
	items i STRAIGHT_JOIN items_img ii ON i.id = ii.item_id
	LEFT JOIN ( SELECT item_id, MIN( price_discount ) AS price_discount FROM items_spec GROUP BY item_id ) tempSpec ON i.id = tempSpec.item_id 
WHERE
	ii.is_main = 1 
	AND i.item_name LIKE '%好吃蛋糕甜点蒸蛋糕%' 
ORDER BY
	i.sell_counts DESC 
	LIMIT 10;

在这里插入图片描述

可以看到type是index,key是items_sell_counts_item_name_index,表示使用了刚刚创建的索引items_sell_counts_item_name_index,而且Extra中显示Using index表示使用了覆盖索引

另外,Extra中还显示Backward index scan;是MySQL8.0的优化表示从索引的后面往前面扫描,因为这里ORDER BY i.sell_counts DESC,而刚刚创建的索引没有指定排序,默认情况下ASC。如果MySQL版本大于等于8.0,可以修改该索引为降序排序提升性能(降序索引是MySQL8.0之后支持的,之前语法支持,效果不支持)

ALTER TABLE items ADD INDEX items_sell_counts_item_name_index (sell_counts DESC, item_name) ;

再次执行EXPLAIN查看

在这里插入图片描述

再次执行sql查看运行耗时可以发现只花费了0.01s

修改SQL一(应对多种排序规则)
SELECT
	i.id AS itemId,
	i.item_name AS itemName,
	i.sell_counts AS sellCounts,
	ii.url AS imgUrl,
	tempSpec.price_discount AS price 
FROM
	items i STRAIGHT_JOIN items_img ii ON i.id = ii.item_id
	LEFT JOIN ( SELECT item_id, MIN( price_discount ) AS price_discount FROM items_spec GROUP BY item_id ) tempSpec ON i.id = tempSpec.item_id 
WHERE
	ii.is_main = 1 
	AND i.item_name LIKE '%好吃蛋糕甜点蒸蛋糕%' 
ORDER BY
	i.item_name ASC 
	LIMIT 10;
	
-- 此时需要0.041s
优化方案

在之前优化的基础上,再额外给items表添加一个索引

ALTER TABLE items ADD INDEX items_item_name_sell_counts_index (item_name, sell_counts) ;

再次执行发现只需要0.027s

修改SQL二(应对多种排序规则)
SELECT
	i.id AS itemId,
	i.item_name AS itemName,
	i.sell_counts AS sellCounts,
	ii.url AS imgUrl,
	tempSpec.price_discount AS price 
FROM
	items i STRAIGHT_JOIN items_img ii ON i.id = ii.item_id
	LEFT JOIN ( SELECT item_id, MIN( price_discount ) AS price_discount FROM items_spec GROUP BY item_id ) tempSpec ON i.id = tempSpec.item_id 
WHERE
	ii.is_main = 1 
	AND i.item_name LIKE '%好吃蛋糕甜点蒸蛋糕%' 
ORDER BY
	tempSpec.price_discount ASC 
	LIMIT 10;
	
-- 此时需要0.218s
优化方案一

执行EXPLAIN查看

在这里插入图片描述

可以看到操作i表的Extra中出现了Using filesort,故可以参考前面的优化方案优化

比如

-- 调大sort_buffer_size -> 145ms
set sort_buffer_size = 4 * 1024 * 1024;

-- 再次执行上面的SQL语句,可以发现花费了0.052s
优化方案二

终极优化方案:反模式设计,引入冗余,把商品的最低优惠价(MIN(price_discount))冗余到items表

这样的好处:SELECT item_id, MIN( price_discount ) AS price_discount FROM items_spec GROUP BY item_idzh这个子查询不需要了。sql简单了。同时排序也可以使用索引,后续优化这条sql也要简单很多

总结
  • 如何阅读EXPLAIN结果
  • 利用松散索引扫描优化GROUP BY
  • 利用STRAIGHT_JOIN强制指定JOIN的顺序
  • 利用索引避免排序

SQL调优实战2-激进调优

SELECT
	i.id AS itemId,
	i.item_name AS itemName,
	i.sell_counts AS sellCounts,
	ii.url AS imgUrl,
	tempSpec.price_discount AS price 
FROM
	items i STRAIGHT_JOIN items_img ii ON i.id = ii.item_id
	LEFT JOIN ( SELECT item_id, MIN( price_discount ) AS price_discount FROM items_spec GROUP BY item_id ) tempSpec ON i.id = tempSpec.item_id 
WHERE
	ii.is_main = 1 
	AND i.item_name LIKE '%好吃蛋糕甜点蒸蛋糕%' 
ORDER BY
	i.sell_counts DESC
	LIMIT 10;
激进优化方案一

我们知道,全模糊是用不了索引的,而右模糊是可以使用索引的。因此,如果业务上允许的话,尽量使用右模糊,避免全模糊

注意:

代码中的sql写法

<select id="searchItems" parameterType="Map" resultType="com.imooc.pojo.vo.SearchItemsVO">
  SELECT
    i.id as itemId,
    i.item_name as itemName,
    i.sell_counts as sellCounts,
    ii.url as imgUrl,
    tempSpec.price_discount as price
  FROM
    items i
  LEFT JOIN
    items_img ii
  on
    i.id = ii.item_id
  LEFT JOIN
    (SELECT item_id, MIN(price_discount) as price_discount FROM items_spec GROUP BY item_id) tempSpec
  on
    i.id = tempSpec.item_id
  WHERE
    ii.is_main = 1
    <if test=" paramsMap.keywords != null and paramsMap.keywords != '' ">
      AND i.item_name like '%${paramsMap.keywords}%' /*两个%因为要拼接到paramsMap.keywords里,所以必须使用$,不能使用#*/
    </if>
    order by
    <choose>
      <when test=" paramsMap.sort == &quot;c&quot; ">
        i.sell_counts desc
      </when>
      <when test=" paramsMap.sort == &quot;p&quot; ">
        tempSpec.price_discount asc
      </when>
      <otherwise>
        i.item_name asc
      </otherwise>
    </choose>
</select>

可以看到like写法是like '%${paramsMap.keywords}%'

$符号是字符串替换符,就引发sql注入,比如传入xxx%'; drop table users; select * from items where item_name like 'yyy%

这样sql就变成了

SELECT
	i.id AS itemId,
	i.item_name AS itemName,
	i.sell_counts AS sellCounts,
	ii.url AS imgUrl,
	tempSpec.price_discount AS price 
FROM
	items i STRAIGHT_JOIN items_img ii ON i.id = ii.item_id
	LEFT JOIN ( SELECT item_id, MIN( price_discount ) AS price_discount FROM items_spec GROUP BY item_id ) tempSpec ON i.id = tempSpec.item_id 
WHERE
	ii.is_main = 1 
	AND i.item_name LIKE 'xxx%'; drop table users; select * from items where item_name like 'yyy%' 
ORDER BY
	i.sell_counts DESC
	LIMIT 10;

直接删除了users表,风险巨大!

可以修改为

<select id="searchItems" parameterType="Map" resultType="com.imooc.pojo.vo.SearchItemsVO">
  SELECT
    i.id as itemId,
    i.item_name as itemName,
    i.sell_counts as sellCounts,
    ii.url as imgUrl,
    tempSpec.price_discount as price
  FROM
    items i
  LEFT JOIN
    items_img ii
  on
    i.id = ii.item_id
  LEFT JOIN
    (SELECT item_id, MIN(price_discount) as price_discount FROM items_spec GROUP BY item_id) tempSpec
  on
    i.id = tempSpec.item_id
  WHERE
    ii.is_main = 1
    <if test=" paramsMap.keywords != null and paramsMap.keywords != '' ">
      AND i.item_name like CONCAT('%', #{paramsMap.keywords}, '%')
    </if>
    order by
    <choose>
      <when test=" paramsMap.sort == &quot;c&quot; ">
        i.sell_counts desc
      </when>
      <when test=" paramsMap.sort == &quot;p&quot; ">
        tempSpec.price_discount asc
      </when>
      <otherwise>
        i.item_name asc
      </otherwise>
    </choose>
</select>
激进优化方案二

彻底使用冗余优化SQL

具体操作:

– 把商品的最低优惠价,直接冗余到items表
– 把商品主图也冗余到items表【商品主图字段较大,实际项目中如果要冗余较大的字段,应该谨慎考虑,看是否有必要】

即在item表新增字段price_discount,和img_url,最后sql可以优化为如下,直接是单表查询了

SELECT
	id,
	item_name,
	sell_counts,
	img_url,
	price_discount 
FROM
	items 
WHERE
	item_name LIKE '%好吃蛋糕甜点蒸蛋糕%' 
ORDER BY
	sell_counts DESC

注意:冗余也不是万能方案,滥用的话在数据增删改时会比较麻烦

激进优化方案三

考虑使用非关系型数据库(elasticsearch/mongodb)

对于商品这样的业务,不需要严格的事务支持,但是性能却是硬需求,因此可以存储到非关系型数据库(elasticsearch/mongodb)

注意:引用非关系型数据库(elasticsearch/mongodb)也不是万能方案,会带来额外的学习成本、使用成本、运维成本

但是就目前来说,各种非关系型数据库正在变得越来越流行,并且电商项目中的商品中心大多数就是使用elasticsearch存储数据的,需要根据业务场景选择合适的数据库

激进优化方案四

业务妥协

业务妥协可以把比较难优化的问题变成不是问题

比如,如果业务能够接受不展示最低优惠价,子查询SELECT item_id, MIN(price_discount) as price_discount from items_spec GROUP BY item_id可以直接不要。变成两表连接查询。当然,此场景不可能不展示最低优惠价

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值