对于初级程序开发工程师而言,SQL是很多人的弱项,为此我给大家来做一下总结,希望能够帮到你们。
1、介绍项目实战开发过程中常用的MySQL函数及常用语法,并且分析三种联合查询原理及如何使用、什么时候使用;
2、针对数据库层面的优化方案做介绍说明;
说明:这里用大家熟知的电商业务来讲解,进一步巩固学习SQL
订单表
CREATE TABLE `tb_order` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '订单id',
`user_id` int(11) NOT NULL COMMENT '用户id',
`price` int(11) NOT NULL COMMENT '订单金额',
`pay_time` datetime DEFAULT NULL COMMENT '支付时间',
`create_time` datetime NOT NULL COMMENT '创建时间',
`status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '订单状态',
`update_time` datetime DEFAULT NULL COMMENT '最后更新时间',
PRIMARY KEY (`id`),
KEY `IDX_USER_ID` (`user_id`),
KEY `IDX_PAY_TIME` (`pay_time`),
KEY `IDX_CREATE_TIME` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 CHECKSUM=1 DELAY_KEY_WRITE=1 ROW_FORMAT=DYNAMIC COMMENT='订单表'
商品表
CREATE TABLE `tb_goods` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品id',
`name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称',
`price` int(11) NOT NULL DEFAULT '0' COMMENT '单价',
`stock` int(11) NOT NULL DEFAULT '0' COMMENT '库存',
`status` smallint(6) NOT NULL DEFAULT '0' COMMENT '状态',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 CHECKSUM=1 DELAY_KEY_WRITE=1 ROW_FORMAT=DYNAMIC COMMENT='商品表';
-- goods_2表
CREATE TABLE `tb_goods_2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) DEFAULT NULL,
`code` varchar(16) DEFAULT NULL COMMENT '商品编码',
`business_id` int(11) DEFAULT NULL COMMENT '商家id',
`price` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `IDX_B_C` (`business_id`,`code`),
KEY `IDX_CODE` (`code`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 CHECKSUM=1 DELAY_KEY_WRITE=1 ROW_FORMAT=DYNAMIC
由于一个订单可能包含多种商品,故存在
订单商品关系表
CREATE TABLE `tb_order_goods` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '关联数据id',
`order_id` int(11) NOT NULL COMMENT '订单id',
`goods_id` int(11) NOT NULL COMMENT '商品id',
`goods_num` int(11) NOT NULL COMMENT '商品数量',
`goods_price` int(11) NOT NULL COMMENT '商品单价',
`sum_price` int(11) NOT NULL COMMENT '小计金额',
PRIMARY KEY (`id`),
KEY `IDX_ORDER_ID` (`order_id`),
KEY `IDX_GOODS_ID` (`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 CHECKSUM=1 DELAY_KEY_WRITE=1 ROW_FORMAT=DYNAMIC COMMENT='商品订单关系表'
用户表
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户id',
`name` varchar(32) DEFAULT NULL COMMENT '姓名',
`phone` varchar(15) NOT NULL DEFAULT '' COMMENT '电话',
`sex` varchar(1) DEFAULT NULL COMMENT '性别',
`password` varchar(64) DEFAULT NULL COMMENT '密码',
`user_name` varchar(18) NOT NULL COMMENT '用户名',
`status` tinyint(3) DEFAULT NULL COMMENT '状态',
PRIMARY KEY (`id`),
KEY `IDX_PHONE` (`phone`),
KEY `IDX_USER_NAME` (`user_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 CHECKSUM=1 DELAY_KEY_WRITE=1 ROW_FORMAT=DYNAMIC COMMENT='用户表';
-- user_2表
CREATE TABLE `tb_user_2` (
`id` int(11) NOT NULL,
`name` varchar(32) DEFAULT NULL,
`password` varchar(32) DEFAULT NULL,
`sex` varchar(1) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `IDX_NAME` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 CHECKSUM=1 DELAY_KEY_WRITE=1 ROW_FORMAT=DYNAMIC COMMENT='用户表2'
有的同学想要知道,针对已存在的表结构,如何添加索引呢?简单演示如下
给tb_user表name字段添加普通索引
-- IDX_NAME 为索引名,可自行定义
ALTER TABLE `tb_user` ADD INDEX `IDX_NAME` (`name`);
INSERT INTO `tb_goods`(`id`,`name`,`price`,`stock`,`status`) VALUES (1,'华为P30',600000,20,1),(2,'美特斯邦威男士夹克衫',29900,100,1);
INSERT INTO `tb_order`(`id`,`user_id`,`price`,`pay_time`,`create_time`,`status`,`update_time`) VALUES (1,1,629900,'2020-02-01 00:00:00','2020-02-01 00:00:00',1,'2020-02-01 00:00:00'),(2,1,1200000,NULL,'2020-02-02 00:00:00',0,'2020-02-02 00:00:00'),(3,2,600000,'2020-03-01 00:00:00','2020-03-01 00:00:00',1,'2020-03-01 00:00:00');
INSERT INTO `tb_order_goods`(`id`,`order_id`,`goods_id`,`goods_num`,`goods_price`,`sum_price`) VALUES (1,1,1,1,600000,600000),(2,1,2,1,29900,29900),(3,2,1,2,600000,1200000),(4,3,1,1,600000,600000);
INSERT INTO `tb_user`(`id`,`name`,`phone`,`sex`,`password`,`user_name`,`status`) VALUES (1,'高M','18810531708','男','123456','gao_yue',1),(2,'明哥','18810532526','男','123654','tang_',1),(3,'培哥','15544455665','男','545466','pei_',1);
COUNT(值),这个值如果不是null则计1,值为null则计0
所以select count(1) from dual;select count(10000) from dual
其结果都为1
而select count(null) from dual
结果则为0
例如tb_user表中有三条数据,其中有一条数据的name字段为空,那么存在
select count(name) from tb_user
结果则为2而非3,如果想要统计表中数据条数则用count(*)或count(id)select 1000 from tb_user
表,其结果为3行1000,所以存在如下select count(1000) from tb_user
结果为3count结合distinct使用可统计某个字段出现(除空值之外的)不同值的次数
比如查询订单表有多少笔金额相同的订单
select count(distinct(price)) from tb_order;
SUM(值),会把这个值的数值进行一个累加,sum(null)则为null
所以select sum(0) from dual
结果为0,select sum(1000) from dual
结果为1000
而select sum(null) from dual
结果为null
故针对sum的字段如果可能出现null的话,需要提前做判断转换IFNULL
(字段,代替值),避免出现统计错误
比如统计订单表订单销售总额(不区分订单状态)
select sum(IFNULL(price,0)) from tb_order;
比如我们要查询所有下过单的用户id集合,这里结果集中针对相同id要做去重处理
SELECT DISTINCT user_id FROM tb_order;
按某个字段分组——GROUP BY
分组之后,对于分组统计的结果再做条件筛选——HAVING
对于结果集按照某个字段进行排序——ORDER BY
select
s1.a, s1.b, s1.c, s2.e, s2.f
from
(select a, b, c from t1 where **) s1
*** join
(select a, e, f from t2 where **) s2
on s1.a = s2.a
...
-- oracle数据库可以在查询语句中使用rownum关键字来获取结果集的行号
select rownum,id,name from student;
SELECT @rowNum:=@rowNum + 1 AS '行号',a.* FROM tableName a,(SELECT @rowNum:=0) b
-- 按订单金额排名 1、2、3...
SELECT
@rowNum:=@rowNum + 1 AS '行号', -- 名次
a.* FROM tb_order a,
(SELECT @rowNum:=0) b
ORDER BY price DESC;
-- 统计 订单表中 未支付订单数、已支付订单数
SELECT
COUNT(CASE WHEN `status` = 0 THEN 1 ELSE NULL END) AS '未支付订单数',
COUNT(CASE WHEN `status` = 1 THEN 1 ELSE NULL END) AS '已支付订单数'
FROM tb_order;
-- 查询订单表 各个支付状态 对应的 订单数
SELECT
(CASE WHEN `status` = 0 THEN '未支付' WHEN `status` = 1 THEN '已支付' END) AS '支付状态',
COUNT(id) AS '订单数'
FROM
tb_order
GROUP BY `status`;
order by
-- 结合
limit 1
-- 一起使用
--查询订单表 金额最大订单 对应的用户信息
SELECT
s2.name,
s2.id
FROM
(SELECT user_id FROM tb_order ORDER BY price DESC LIMIT 1) s1
INNER JOIN
tb_user s2
ON s2.id = s1.user_id
查询当前日期时间
查询当前日期、当前时间
SELECT NOW(),CURRENT_DATE,CURRENT_TIME
日期格式化操作,常用
-- 按日统计订单数,数据量过大会导致慢查询,就算create_time是建立了索引的,建议优化
-- 优化方向
-- 1、缩小范围
-- 2、冗余年月日字段
-- 3、数据同步,数据仓库,报表中台。不要从实时业务生产数据库查询统计相关数据
SELECT
DATE_FORMAT(create_time,'%Y-%m-%d') AS '日期',
COUNT(id) AS '下单量',
SUM(IFNULL(price,0)) AS '下单金额',
MAX(price) AS '最大订单金额'
FROM tb_order
GROUP BY DATE_FORMAT(create_time,'%Y-%m-%d');
SELECT DATE_FORMAT(NOW(),'%Y.%m.%d %T');
-- 获取 dateTime 字段时间戳
SELECT UNIX_TIMESTAMP(NOW());
查询某个日期之前、之后
# 日期 操作的 函数 date_add、date_sub
-- 三分钟之后
SELECT DATE_ADD(NOW(),INTERVAL 3 MINUTE);
-- 30个月之前
SELECT DATE_SUB(NOW(),INTERVAL 30 MONTH);
-- 3天之前
SELECT DATE_SUB(NOW(),INTERVAL 3 DAY);
-- 指定某个日期查询其之前或之后
SELECT DATE_SUB('2019-08-09 12:08:08',INTERVAL 3 DAY);
拼接字符串
-- 用于拼接字符串的参数个数不限定
SELECT CONCAT('a','bcd','efg') -- abcdefg
SELECT CONCAT(id,`name`) FROM tb_user;
截取字符串
SELECT SUBSTR('12345',1,3); -- 123
SELECT SUBSTR('12345',3); -- 345
从右边、左边开始 截取多少位字符串
SELECT LEFT('2019-08-09 12:08:08',10);
SELECT LEFT(create_time,10) FROM tb_order; -- 年-月-日
SELECT RIGHT(create_time,8) FROM tb_order; -- 时:分:秒
-- 可以达到 跟 DATE_FORMAT相同的目的
SELECT DATE_FORMAT('2019-08-09 12:08:08','%Y-%d-%m');
-- 用sql把用户表的性别为男的变成女的,女的变成男的
-- 可以用case when
UPDATE
tb_user_2
SET
sex = (CASE WHEN sex = '女' THEN '男' WHEN sex = '男' THEN '女' END);
-- 推荐使用IF
-- 用IF,当表达式条件成立,则用第一个值,否则用第二个值。类似 Java中的 三元运算
UPDATE
tb_user_2
SET
sex = IF(sex='女','男','女'); -- 这里的 '男' 为第一个值,'女' 为第二个值
-- 把订单表中订单金额 (只要用户有任何一个订单金额超过1000即可) 超过1000的用户的状态更新为999
-- 首先需要确认的是,使用内连接。
-- 中间结果集
SELECT t1.*,t2.* FROM
tb_user t1
INNER JOIN # 内联接
tb_order t2
ON t1.id = t2.user_id;
-- 具体更新操作
UPDATE
tb_user t1
INNER JOIN
tb_order t2
ON t1.id = t2.user_id
SET
t1.status = 999
WHERE
t2.price > 1000;
-- 优化版本,减少更新判断的次数
-- 中间结果集
SELECT t1.*,t2.* FROM
tb_user t1
INNER JOIN # 内联接
(SELECT user_id,MAX(price) FROM tb_order GROUP BY user_id HAVING MAX(price)>1000) t2
ON t1.id = t2.user_id;
-- 具体更新操作
UPDATE
tb_user t1
INNER JOIN
(SELECT user_id,MAX(price) FROM tb_order GROUP BY user_id HAVING MAX(price)>1000) t2
ON t1.id = t2.user_id
SET
t1.status = 999;
-- 搞个额外的活动,找出在我们平台下过单的用户,当订单总金额超过100W,把用户变成超级VIP -- 100
-- 需要用到分组聚合
-- 中间结果集
SELECT t1.*,t2.* FROM
tb_user t1
INNER JOIN -- 内联接
(SELECT user_id,SUM(IFNULL(price,0)) FROM tb_order GROUP BY user_id HAVING SUM(price)>1000000) t2
ON t1.id = t2.user_id;
-- 具体更新操作
UPDATE
tb_user t1
INNER JOIN -- 内联接
(SELECT user_id,SUM(IFNULL(price,0)) FROM tb_order GROUP BY user_id HAVING SUM(price)>1000000) t2
ON t1.id = t2.user_id
SET
t1.status = 100;
-- 将查询的 结果集 直接导入某个表
INSERT INTO tb_user_2
SELECT
tb_user.id,
tb_user.name,
tb_user.password,
tb_user.sex AS 'sex'
FROM tb_user
WHERE
sex = '女';
把多个结果集进行整合,整合成一个结果集,要求每个结果集字段一致
union会将全字段完全一样的数据去重,union all则不会去除重复数据
-- UNION 、UNION ALL
-- UNION ALL
(SELECT
tb_user.id,
tb_user.name,
tb_user.password,
tb_user.sex AS 'sex'
FROM tb_user
WHERE
sex = '女')
UNION ALL -- 不会去重
(SELECT * FROM tb_user_2)
-- UNION
(SELECT
tb_user.id,
tb_user.name,
tb_user.password,
tb_user.sex AS 'sex'
FROM tb_user
WHERE
sex = '女')
UNION -- 会去重
(SELECT * FROM tb_user_2)
当某个学生的成绩录入了两次(1对多)时(而如果是多对多,则会出现笛卡尔积)
实则a left join b 跟 b right join a 结果集数据是一致的,不同的是字段的位置而已
以学生表、成绩表为例
查询没有学生成绩的学生姓名;(查看哪一些学生没有交卷)—— student left join score
需要查询学生姓名,那么学生信息不能为空;
需要查询的学生是没有成绩的,那么成绩需要为空(null);
查询成绩最好的学生姓名;—— student inner join score
成绩最好:说明这个学生肯定有成绩,这个学生id必须在成绩表中存在;
姓名:说明这个学生在学生表里面肯定也存在;
那么用inner join
-- 联合索引 按左 匹配原则
--比如 某个联合索引 由 a,b,c 构成
-- 如下查询无法命中索引
SELECT * FROM tb_ WHERE b = **;
SELECT * FROM tb_ WHERE c = **;
SELECT * FROM tb_ WHERE c = ** AND b = ***;
SELECT * FROM tb_ WHERE c = ** AND a = ***;
--要想 c 这部分 索引能够生效使用,必须要求 查询的 条件中 有 b 字段 这个条件
--要想 b 这部分 索引能够生效使用,必须要求 查询的 条件中 有 a 字段 这个条件
SELECT * FROM tb_ WHERE b = ** AND a = ***; -- 查询能够命中联合索引 的 a、b 部分
SELECT * FROM tb_ WHERE c = ** AND b = *** AND a = ****; -- 查询能够命中联合索引 的 a、b、c 部分
-- 联合索引 最左 匹配的原则
-- IDX_B_C business_id, code ——从业务转化而来
-- 业务中,有大量的关于 code的查询,都是 一定会携带 business_id
-- 更形象的例子,我们是一个B 平台,有很多小B入驻
-- 每个小B在我们平台给他们提供的后台管理系统中,他们 只能 访问 自己的商品数据,
-- 必定在查寻数据的时候会携带商家id,我们后台肯定知道当前登陆的用户是谁,也就知道了是哪个商家
减轻关系型数据库访问压力,提升程序负载能力;(程序的瓶颈一般是体现在数据库访问的瓶颈之上)
加快响应速度,提升用户体验;
慢查询引起的生产事故;因为慢查询的存在(也有可能是数据库、表结构设计不合理导致的)导致一些非常简单的sql语句都没法执行。从而引起生产问题。
表结构重构、数据迁移是巨大的工程;拓展性不好
想到业务后期有可能发展成什么样子,提前将这种关系考虑到,然后再进行表结构设计
防止后期拓展字段需要变动数据库表结构,可以如此设计:冗余一个 expand varchar 510 ----- json 对象 字符串
关系型数据库针对某些数据的存储不是那么合适;
每一列是不可再分的属性值,同一列不能包含多个值,关系型数据库的特点之一;
地址:省、市、区、具体地址
数据库表中每个实例或行必须可以被唯一地区分;
需要有唯一能区分行数据的字段(主键)
一个数据表中不再包含已在其它表中包含的非主关键字信息(看情况);
订单数据,包含用户id,不再存储用户其它数据(看情况),(分布式系统中,比如下单时的用户手机号…)
一主一从,一主多从的架构方式
利用主从数据库来实现读写分离,从而分担主数据库的压力。在多个服务器上部署mysql,将其中一台认为主数据库,而其他为从数据库,实现主从同步。其中主数据库负责主动写的操作,而从数据库则只负责主动读的操作(slave从数据库仍然会被动的进行写操作,为了保持数据一致性),这样就可以很大程度上的避免数据丢失的问题,同时也可减少数据库的连接,减轻主数据库的负载。
https://blog.csdn.net/qq_15092079/article/details/81672920
主库用来写数据,可读可写
从库只允许读数据,不允许写数据
同步复制方案效率较低,一般采用默认同步方式:异步复制
在并发访问较大的情况下,导致数据库服务压力飙升,可能导致主从数据同步存在一定的延迟,需要从业务的角度做适配。或者采取指定主库查询的方式来避免这种问题。但是我们作为技术开发人员,需要知道这个问题是肯定存在的。
缓存是将数据从数据库(数据在磁盘上)复制到内存中,用户访问数据时,直接从内存中获取数据。这样就可以提升数据访问的效率,提升系统的并发访问量,提升用户体验,提升整个系统的吞吐量。是互联网产品技术实现中经常采用的方案。
详见:https://www.cnblogs.com/yueyun00/p/10898677.html
如SQL语句:select * from tb_user where id = 10010;
在MySQL服务执行完以后,MySQL会从磁盘查询到具体的数据返回客户端,并且将【sql:查询的结果集】进行映射缓存,保存到MySQL服务的内存中;
在tb_user表结构、数据没有发生变动(delete、insert、update…)时,执行相同的SQL语句,会根据SQL直接从内存中获取结果返回,不会再去查找读取磁盘,加快访问速度。当表数据发送变动,会将跟这个表相关的缓存结果数据进行清除。
例如:Java服务本地缓存,缓存在Java进程开辟的内存空间中
优势:速度更快,架构方式更加简单;
劣势:一致性问题,多进程间缓存无法实现共享,缓存的数据量不易拓展,对服务本身的入侵性太强,缓存的可用性得依靠服务自身;
Redis缓存
分布式部署,满足大量数据需要缓存的业务场景,实现分布式服务场景下的缓存共享;
通过单独的缓存服务进行部署维护,缓存的访问需要依靠网络通信;
解决单库数据存储压力过大的问题
把同一个库中的多个表,按照业务进行垂直划分,拆分到多个数据库中,每个库会单独处理具体的业务数据;
一张表数据量太大,破千万甚至过亿,我们不得不将一张表中的数据拆分到多张表中;
具体数据需要存储到哪张表,或者查询时需要从哪张表进行搜索,都需要经过算法操作,一般业界会采用成熟的中间件,来访问我们的数据库,我们作为客户端,只需要使用(访问)中间件即可;(mycat、shardingJDBC…)
非关系型的数据库
存在的意义,填补关系型数据库的不足,为关系型数据库存储做补充;根据K去找对应的V,当然需要经过一个算法分析,当前这个kv数据存储在哪个节点上面;
K:V存储,便于线性拓展(以Redis理解),多用于海量的数据存储(几十亿、百亿数据),日志数据,用户报告数据、用户消费情况、足迹;
典型的代表:MongoDB、Redis、HBASE、TableStore
这类数据库不仅具有NoSQL对海量数据的存储管理能力,还保持了传统数据库支持ACID和SQL等特性;
NewSQL是指这样一类新式的关系型数据库管理系统,针对OLTP(读-写)工作负载,追求提供和NoSQL系统相同的扩展性能,且仍然保持ACID和SQL等特性;
NewSQL系统虽然在的内部结构变化很大,但是它们有两个显着的共同特点:(1)它们都支持关系数据模型,(2) 它们都使用SQL作为其主要的接口。
定期清理业务不需要使用的数据(历史数据),从实时业务数据库表中移除,保存到历史表,减轻实时业务生成数据表的压力;
比如:十年前的 订单数据;从具体业务层面来做数据归档操作;
最高层面的一种数据库的优化手段。只是针对的对数据有需求的 业务 做的 优化手段;
我们的所有关于数据分析类的查询,都不再查询实时业务生产数据库,或者是说将搜索业务从实时业务生成数据库进行剥离,从源头上解决数据需求的问题!
用空间来换时间的一种操作方式。
数据如何同步到数据仓? 多采用 异步收集,canal、maxwell、日志、定时任务查询(凌晨)
存在的问题:数据一致性问题,存储的问题,可采用NoSQL等数据库做适应性存储;一致性的问题,需要从业务角度做适配;
非结构化的数据(分散的数据) ----> 结构化的数据(具体业务能够直接使用的数据)
事务型数据库的首选引擎
实时业务生产数据库表存储引擎
拥有较高的插入、查询速度,但不支持事物
非实时业务生产,如 数据仓库 等 数据应用数据库环境
临时存放数据,数据量不大,并且不需要较高的数据安全性,效率高;
MySQL中使用该引擎作为临时表,存放查询的中间结果。