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 == "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就变成了
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 == "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可以优化为如下,直接是单表查询了
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
可以直接不要。变成两表连接查询。当然,此场景不可能不展示最低优惠价