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))
BEGINDECLARE i INT DEFAULT 0;loopname:LOOPSET 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_timefrom carousel;insert into category (name, type, father_id, logo, slogan, cat_image, bg_color)select name, type, father_id, logo, slogan, cat_image, bg_colorfrom 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_timefrom 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_timefrom 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_timefrom 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_timefrom 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_timefrom 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_countsfrom 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_timefrom 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_timefrom orders;insert into stu (name, age)select name, agefrom 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_timefrom 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_timefrom users;IF i = repeat_times THENLEAVE 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
SELECTi.id AS itemId,i.item_name AS itemName,i.sell_counts AS sellCounts,ii.url AS imgUrl,tempSpec.price_discount AS price
FROMitems i STRAIGHT_JOIN items_img ii ON i.id = ii.item_idLEFT JOIN ( SELECT item_id, MIN( price_discount ) AS price_discount FROM items_spec GROUP BY item_id ) tempSpec ON i.id = tempSpec.item_id
WHEREii.is_main = 1 AND i.item_name LIKE '%好吃蛋糕甜点蒸蛋糕%'
ORDER BYi.sell_counts DESC LIMIT 10;-- 优化之前需要花费0.437s
具体优化操作
使用EXPLAIN分析
EXPLAIN SELECTi.id AS itemId,i.item_name AS itemName,i.sell_counts AS sellCounts,ii.url AS imgUrl,tempSpec.price_discount AS price
FROMitems iLEFT JOIN items_img ii ON i.id = ii.item_idLEFT JOIN ( SELECT item_id, MIN( price_discount ) AS price_discount FROM items_spec GROUP BY item_id ) tempSpec ON i.id = tempSpec.item_id
WHEREii.is_main = 1 AND i.item_name LIKE '%好吃蛋糕甜点蒸蛋糕%'
ORDER BYi.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 SELECTi.id AS itemId,i.item_name AS itemName,i.sell_counts AS sellCounts,ii.url AS imgUrl,tempSpec.price_discount AS price
FROMitems i STRAIGHT_JOIN items_img ii ON i.id = ii.item_idLEFT JOIN ( SELECT item_id, MIN( price_discount ) AS price_discount FROM items_spec GROUP BY item_id ) tempSpec ON i.id = tempSpec.item_id
WHEREii.is_main = 1 AND i.item_name LIKE '%好吃蛋糕甜点蒸蛋糕%'
ORDER BYi.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一(应对多种排序规则)
SELECTi.id AS itemId,i.item_name AS itemName,i.sell_counts AS sellCounts,ii.url AS imgUrl,tempSpec.price_discount AS price
FROMitems i STRAIGHT_JOIN items_img ii ON i.id = ii.item_idLEFT JOIN ( SELECT item_id, MIN( price_discount ) AS price_discount FROM items_spec GROUP BY item_id ) tempSpec ON i.id = tempSpec.item_id
WHEREii.is_main = 1 AND i.item_name LIKE '%好吃蛋糕甜点蒸蛋糕%'
ORDER BYi.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二(应对多种排序规则)
SELECTi.id AS itemId,i.item_name AS itemName,i.sell_counts AS sellCounts,ii.url AS imgUrl,tempSpec.price_discount AS price
FROMitems i STRAIGHT_JOIN items_img ii ON i.id = ii.item_idLEFT JOIN ( SELECT item_id, MIN( price_discount ) AS price_discount FROM items_spec GROUP BY item_id ) tempSpec ON i.id = tempSpec.item_id
WHEREii.is_main = 1 AND i.item_name LIKE '%好吃蛋糕甜点蒸蛋糕%'
ORDER BYtempSpec.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-激进调优
SELECTi.id AS itemId,i.item_name AS itemName,i.sell_counts AS sellCounts,ii.url AS imgUrl,tempSpec.price_discount AS price
FROMitems i STRAIGHT_JOIN items_img ii ON i.id = ii.item_idLEFT JOIN ( SELECT item_id, MIN( price_discount ) AS price_discount FROM items_spec GROUP BY item_id ) tempSpec ON i.id = tempSpec.item_id
WHEREii.is_main = 1 AND i.item_name LIKE '%好吃蛋糕甜点蒸蛋糕%'
ORDER BYi.sell_counts DESCLIMIT 10;
激进优化方案一
我们知道,全模糊是用不了索引的,而右模糊是可以使用索引的。因此,如果业务上允许的话,尽量使用右模糊,避免全模糊
注意:
代码中的sql写法
<select id="searchItems" parameterType="Map" resultType="com.imooc.pojo.vo.SearchItemsVO">SELECTi.id as itemId,i.item_name as itemName,i.sell_counts as sellCounts,ii.url as imgUrl,tempSpec.price_discount as priceFROMitems iLEFT JOINitems_img iioni.id = ii.item_idLEFT JOIN(SELECT item_id, MIN(price_discount) as price_discount FROM items_spec GROUP BY item_id) tempSpeconi.id = tempSpec.item_idWHEREii.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 == "c" ">i.sell_counts desc</when><when test=" paramsMap.sort == "p" ">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就变成了
SELECTi.id AS itemId,i.item_name AS itemName,i.sell_counts AS sellCounts,ii.url AS imgUrl,tempSpec.price_discount AS price
FROMitems i STRAIGHT_JOIN items_img ii ON i.id = ii.item_idLEFT JOIN ( SELECT item_id, MIN( price_discount ) AS price_discount FROM items_spec GROUP BY item_id ) tempSpec ON i.id = tempSpec.item_id
WHEREii.is_main = 1 AND i.item_name LIKE 'xxx%'; drop table users; select * from items where item_name like 'yyy%'
ORDER BYi.sell_counts DESCLIMIT 10;
直接删除了users表,风险巨大!
可以修改为
<select id="searchItems" parameterType="Map" resultType="com.imooc.pojo.vo.SearchItemsVO">SELECTi.id as itemId,i.item_name as itemName,i.sell_counts as sellCounts,ii.url as imgUrl,tempSpec.price_discount as priceFROMitems iLEFT JOINitems_img iioni.id = ii.item_idLEFT JOIN(SELECT item_id, MIN(price_discount) as price_discount FROM items_spec GROUP BY item_id) tempSpeconi.id = tempSpec.item_idWHEREii.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 == "c" ">i.sell_counts desc</when><when test=" paramsMap.sort == "p" ">tempSpec.price_discount asc</when><otherwise>i.item_name asc</otherwise></choose>
</select>
激进优化方案二
彻底使用冗余优化SQL
具体操作:
– 把商品的最低优惠价,直接冗余到items表
– 把商品主图也冗余到items表【商品主图字段较大,实际项目中如果要冗余较大的字段,应该谨慎考虑,看是否有必要】
即在item表新增字段price_discount,和img_url,最后sql可以优化为如下,直接是单表查询了
SELECTid,item_name,sell_counts,img_url,price_discount
FROMitems
WHEREitem_name LIKE '%好吃蛋糕甜点蒸蛋糕%'
ORDER BYsell_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
可以直接不要。变成两表连接查询。当然,此场景不可能不展示最低优惠价