MYSQL必知必会,详尽入门,一文帮你学会SQL必知必会
Posted 小杰312
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MYSQL必知必会,详尽入门,一文帮你学会SQL必知必会相关的知识,希望对你有一定的参考价值。
目录
alter :修改现有的数据库对象,例如 修改表的属性或者字段 (列)
前言
- 各位友友们, 本文可能文章会很长,是小杰对于自己数据库入门基操所学的总结随笔, 是小杰学习数据库以来的一个总和吧, 学会这些数据库的基本的各种操作, 怎删改查基本上是没有什么问题了, 但是基本仅仅也只是适合像小杰一样的数据库入门选手,像查询优化, 事务, 触发器, SQL语句执行过程等等性能优化方面的东西本文没有涉及, 后序如果小杰学到了也会总结出来跟大家做分享的,
- 文章很长,希望友友们可以耐心读下去, 感谢您的阅读,你们的阅读,探讨,分享一直是小杰创作的动力
数据库的概念和术语
- 按照数据结构来组织、存储和管理数据的仓库;是一个长期存储在计算机内的、有组织的、可共享 的、统一管理的大量数据的集合
- 抓住 长期存储 + 方便管理大量数据 (支持并发访问) 相比磁盘更加快速
- 数据库:数据库是一些关联表的集合;
- 数据表:表是数据的矩阵;
- 列:一列包含相同类型的数据;
- 行:或者称为记录是一组相关的数据;
- 主键:主键是唯一的;一个数据表只能包含一个主键; (primary key 非空去重唯一性)
- 外键:外键用来关联两个表,来保证参照完整性;MyISAM存储引擎本身并不支持外键,只起到注 释作用;而innodb完整支持外键; (foreign key 存储在从表, 从表和主表的关系列,关联列)
- 复合键:或称组合键;将多个列(字段) 指定为一个索引键;
- 索引:用于快速访问数据表的数据;索引是对表中的一列或者多列的值进行排序的一种结构; (抓住. 索引是为了方便排序的一列或多列数据)
SQL语言和组成
- 用于存取数据以及查询、更新和管理关系数据库系统。SQL是关系数据 库系统的标准语言
- 抓住一点, SQL就是用来操作数据库的一门语言就OK,关系型数据库标准语言
- SQL 命令包含: DQL、DML、DDL、DCL以及TCL 本文仅介绍实操部分
DDL
- Data Define Languge - 数据定义语言, 用于定义 数据库,数据表
show : 展示当前已有的数据库或者表
- show databases; 显示所有数据库
show databases; # 显示所有数据库 +--------------------+ | Database | +--------------------+ | information_schema | | hello | | mysql | | performance_schema | | sys | | test | | xiaojie | +--------------------+ 7 rows in set (0.00 sec)
- show tables; 显示当前选择数据库的所有数据表
use tangyujie; Database changed show tables; Empty set (0.00 sec)
- use 数据库名称; 指定选择一个数据库, 选择具体的数据库操作
use test; # 指定选择一个数据库 Database changed
create :创建一个数据库或者一个表
- create database 数据库名; 不安全的创建一个数据库 (如果表已经存在会报错)
- 加 if not exists 检测, 如果数据库已存在就不创建,否则创建 (不会报错, 只是提示)
create database if not exists tangyujie; # 创建tangyujie数据库如果不存在 Query OK, 1 row affected (0.00 sec) show databases; +--------------------+ | Database | +--------------------+ | information_schema | | hello | | mysql | | performance_schema | | sys | | tangyujie | | test | | xiaojie | +--------------------+ 8 rows in set (0.00 sec)
- create table 表名; 不安全的创建一个表 (如果表已经存在会报错)
- 加 if not exists 检测, 如果数据表已存在就不创建,否则创建 (不会报错, 只是提示)
create table if not exists STU ( id int, name varchar(10), age tinyint ) engine = innodb default charset = utf8mb4; show tables; +---------------------+ | Tables_in_tangyujie | +---------------------+ | stu | +---------------------+ 1 row in set (0.00 sec)
drop :删除表、数据库对象或者视图
- drop table 表名; 不安全的删除一个表 (如果表不存在会报错)
- 加上if exists 检测, 如果表不存在不会报错 (会提示waring)
drop table stu; Query OK, 0 rows affected (0.01 sec) show tables; Empty set (0.00 sec) drop table if exists stu; Query OK, 0 rows affected, 1 warning (0.00 sec)
- drop database 数据库名; 不安全的删除一个库 (如果库不存在会报错)
- 加上if exists 检测, 如果表不存在不会报错 (会提示waring)
- 注意:删库跑路不是吹出来的,不要轻易使用drop
drop database tangyujie; Query OK, 0 rows affected (0.01 sec) show databases; +--------------------+ | Database | +--------------------+ | information_schema | | hello | | mysql | | performance_schema | | sys | | test | | xiaojie | +--------------------+ 7 rows in set (0.00 sec)
alter :修改现有的数据库对象,例如 修改表的属性或者字段 (列)
- desc 数据表名; 展示数据表的表结构
desc stu; +-------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | | name | varchar(10) | YES | | NULL | | | age | tinyint(4) | YES | | NULL | | +-------+-------------+------+-----+---------+-------+ 3 rows in set (0.00 sec)
- alter table 指定表名 add 新列名 列类型(类型长度); 添加一列字段\\
- 新增一列grade varchar(5); 等级列, 成绩等级
alter table stu add grade varchar(5); Query OK, 0 rows affected (0.02 sec) Records: 0 Duplicates: 0 Warnings: 0 desc stu; +-------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | | name | varchar(10) | YES | | NULL | | | age | tinyint(4) | YES | | NULL | | | grade | varchar(5) | YES | | NULL | | +-------+-------------+------+-----+---------+-------+ 4 rows in set (0.00 sec)
- alter table 指定表名 modify 指定列名(字段) 新的类型(新的长度) -- 修改类型
- 将 grade 改为 int 类型的 成绩
alter table stu modify grade int(11); Query OK, 0 rows affected (0.02 sec) Records: 0 Duplicates: 0 Warnings: 0 desc stu; +-------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | | name | varchar(10) | YES | | NULL | | | age | tinyint(4) | YES | | NULL | | | grade | int(11) | YES | | NULL | | +-------+-------------+------+-----+---------+-------+ 4 rows in set (0.00 sec)
- alter table 指定表名 change 旧的列名 新的列名 新类型(类型长度) --修改列名+类型
- 将 grade 列改成 addr 列 类型改成 varchar(10);
alter table stu change grade addr varchar(10); Query OK, 0 rows affected (0.02 sec) Records: 0 Duplicates: 0 Warnings: 0 desc stu; +-------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | | name | varchar(10) | YES | | NULL | | | age | tinyint(4) | YES | | NULL | | | addr | varchar(10) | YES | | NULL | | +-------+-------------+------+-----+---------+-------+ 4 rows in set (0.00 sec)
- alter table 指定表名 drop 指定列名; -- 将指定列删除
alter table stu drop addr; Query OK, 0 rows affected (0.02 sec) Records: 0 Duplicates: 0 Warnings: 0 desc stu; +-------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | | name | varchar(10) | YES | | NULL | | | age | tinyint(4) | YES | | NULL | | +-------+-------------+------+-----+---------+-------+ 3 rows in set (0.00 sec)
DML
- Data Manipulate Language - 数据操作语言 (记录的增删改查)
insert :插入记录
- insert into 指定表(指定列) values(指定值);
- 指定插入一条记录 id 1001 name = 孙悟空 age = 100
insert into stu(id, name, age) values(1001, '孙悟空', 100); Query OK, 1 row affected (0.01 sec) select * from stu; +------+-----------+------+ | id | name | age | +------+-----------+------+ | 1001 | 孙悟空 | 100 | +------+-----------+------+ 1 row in set (0.00 sec)
- values 赋值顺序需要和指定列顺序保持一致
insert into stu(name, id, age) values('沙和尚', 1002, 100); Query OK, 1 row affected (0.00 sec) select * from stu; +------+-----------+------+ | id | name | age | +------+-----------+------+ | 1001 | 孙悟空 | 100 | | 1002 | 沙和尚 | 100 | +------+-----------+------+ 2 rows in set (0.00 sec)
- 不指定列, 默认按照所有列顺序插入 values
insert into stu values(1003, '白龙马', 100); Query OK, 1 row affected (0.00 sec) select * from stu; +------+-----------+------+ | id | name | age | +------+-----------+------+ | 1001 | 孙悟空 | 100 | | 1002 | 沙和尚 | 100 | | 1003 | 白龙马 | 100 | +------+-----------+------+ 3 rows in set (0.00 sec)
update :更新记录
- update 表名 set 字段1 = 值1 字段2 = 值2 where 条件指定记录;
- 指定设置 id = 1003的 记录 name = 猪八戒 (跟新记录)
update stu set name = '猪八戒' where id = 1003; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 select * from stu; +------+-----------+------+ | id | name | age | +------+-----------+------+ | 1001 | 孙悟空 | 100 | | 1002 | 沙和尚 | 100 | | 1003 | 猪八戒 | 100 | +------+-----------+------+ 3 rows in set (0.00 sec)
delete :删除记录
- delete from 表名 where 条件指定记录;
- 指定删除id = 1003的记录
delete from stu where id = 1003; Query OK, 1 row affected (0.00 sec) select * from stu; +------+-----------+------+ | id | name | age | +------+-----------+------+ | 1001 | 孙悟空 | 100 | | 1002 | 沙和尚 | 100 | +------+-----------+------+ 2 rows in set (0.00 sec)
DQL
- Data Query Language - 数据查询语言
select :从一个或者多个表中检索特定的记录
- 数据准备
insert into stu values(1003, '猪八戒', 100); insert into stu values(1004, '白龙马', 100); insert into stu values(1009, '白骨精', 78); insert into stu values(1006, '哪吒', 88); insert into stu values(1004, '观音', 99);
select * from stu; +------+-----------+------+ | id | name | age | +------+-----------+------+ | 1001 | 孙悟空 | 100 | | 1002 | 沙和尚 | 100 | | 1003 | 猪八戒 | 100 | | 1004 | 白龙马 | 100 | | 1006 | 哪吒 | 88 | | 1004 | 观音 | 99 | | 1009 | 白骨精 | 78 | +------+-----------+------+ 7 rows in set (0.00 sec)
select 指定列查询
- select 指定列1, 指定列2, 指定列3... from 表名;
- 指定查询 stu 表中的name + age 列信息
select name, age from stu; +-----------+------+ | name | age | +-----------+------+ | 孙悟空 | 100 | | 沙和尚 | 100 | | 猪八戒 | 100 | | 白龙马 | 100 | | 哪吒 | 88 | | 观音 | 99 | | 白骨精 | 78 | +-----------+------+ 7 rows in set (0.00 sec)
select 条件查询
- select * from 表名 where 条件指定查询记录
- 条件指定查询 id = 1003的记录
select * from stu where id = 1003; +------+-----------+------+ | id | name | age | +------+-----------+------+ | 1003 | 猪八戒 | 100 | +------+-----------+------+ 1 row in set (0.00 sec)
select 去重查询
- select distinct 指定去重列 from 表名;
- 按照id号进行去重查询
select id from stu; +------+ | id | +------+ | 1001 | | 1002 | | 1003 | | 1004 | | 1006 | | 1004 | | 1009 | +------+ 7 rows in set (0.00 sec) select distinct id from stu; +------+ | id | +------+ | 1001 | | 1002 | | 1003 | | 1004 | | 1006 | | 1009 | +------+ 6 rows in set (0.00 sec)
select 排序查询 (查询后排序)
- select * from stu order by 指定列1 排序规则, 指定列2 排序规则;
- 排序规则:默认升序, asc 指定升序 desc 指定降序
- 先按照 id 升序排序, id 相同再按照 age 降序排序
select * from stu order by id asc, age desc; +------+-----------+------+ | id | name | age | +------+-----------+------+ | 1001 | 孙悟空 | 100 | | 1002 | 沙和尚 | 100 | | 1003 | 猪八戒 | 100 | | 1004 | 白龙马 | 100 | | 1004 | 观音 | 99 | | 1006 | 哪吒 | 88 | | 1009 | 白骨精 | 78 | +------+-----------+------+ 7 rows in set (0.00 sec)
select 起别名
- select 指定列1 别名, 指定列2 别名... from stu;
- 将 age 取个别名为年龄
select age '年龄', name from stu; +--------+-----------+ | 年龄 | name | +--------+-----------+ | 100 | 孙悟空 | | 100 | 沙和尚 | | 100 | 猪八戒 | | 100 | 白龙马 | | 88 | 哪吒 | | 99 | 观音 | | 78 | 白骨精 | +--------+-----------+ 7 rows in set (0.00 sec)
select 聚合函数分组查询
- select 聚合函数名(指定列) from 表名; 指定对于表每一列进行聚合函数
- 查询一下表中所有的 age 和 age 最大 最小值
select sum(age) from stu; +----------+ | sum(age) | +----------+ | 665 | +----------+ 1 row in set (0.00 sec) select min(age) from stu; +----------+ | min(age) | +----------+ | 78 | +----------+ 1 row in set (0.00 sec) select max(age) from stu; +----------+ | max(age) | +----------+ | 100 | +----------+ 1 row in set (0.00 sec)
- select 指定分组字段 聚合函数(指定字段) from 表名 group by 指定分组字段
- 注意:分组也就意味着去重,一旦分组之后, 就会按照分组列进行去重,
- 一旦去重之后意味着什么? 我们跟随着去重列一同显示出来的其他列也必须是聚合在一起的
- 上述这句话必须理解清楚,你想想,分组前是很多行,分组之后进行了去重,记录需要从多条记录压缩成一条记录,也就意味着,我们需要聚合多条记录.
- 依照age 进行 分组, 将多条记录聚合压缩成分组记录, 名字同组的联在一起,并且统计同组人数
select age, count(*), group_concat(`name`) from stu group by age; +------+----------+-----------------------------------------+ | age | count(*) | group_concat(`name`) | +------+----------+-----------------------------------------+ | 78 | 1 | 白骨精 | | 88 | 1 | 哪吒 | | 99 | 1 | 观音 | | 100 | 4 | 孙悟空,沙和尚,猪八戒,白龙马 | +------+----------+-----------------------------------------+ 4 rows in set (0.00 sec)
select 分页查询
何为分页 limit n, m; 从 n 条记录开始显示m条记录
- select * from limit 起始记录数, 每页的记录数
- 起始记录数目如何计算, 是每一页的记录数的整数倍
- eg: 查看第一页的记录,和查看第二页的记录 (每页记录数为3)
select * from stu limit 0,3; +------+-----------+------+ | id | name | age | +------+-----------+------+ | 1001 | 孙悟空 | 100 | | 1002 | 沙和尚 | 100 | | 1003 | 猪八戒 | 100 | +------+-----------+------+ 3 rows in set (0.00 sec) select * from stu limit 3,3; +------+-----------+------+ | id | name | age | +------+-----------+------+ | 1004 | 白龙马 | 100 | | 1006 | 哪吒 | 88 | | 1004 | 观音 | 99 | +------+-----------+------+ 3 rows in set (0.00 sec) select * from stu limit 6,3; +------+-----------+------+ | id | name | age | +------+-----------+------+ | 1009 | 白骨精 | 78 | +------+-----------+------+ 1 row in set (0.00 sec)
如上分别是查看第一页 第二页,第三页的 记录
多表理解加基操
多表分类和理解
- why: 项目开发中,在进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表结构之间也存在着各种联系
多表关系分类
一对一关系
案例:用户与用户详细的关系
关系:一对一关系,多用于单表拆分,将一张表的基础字段放在一张表中,其他详情字段放在另一张表中,以提升操作效率 (为避免一张表记录过长的情况,字段太多了就可以进行单表拆分,此时拆分的两个表就是一对一的关系)
实现:在任意一方加入一个外键,关联另一方的主键,并且设置外键为唯一约束
一对多关系 (一个班级多个学生, 一个学生对应一个班级)
案例:部门与员工的关系
关系:一个部门对应多个员工,一个员工对应一个部门
实现:在多的一方建立外键,关联另一方的主键
why ? 我们将部门信息放在员工表中,合并为一个表是不可行吗? 为啥要分开来使用,这样产生的一对多的关系也使得查询等等操作更加复杂.
不是合在一起不行,而是要分析分开的好处, 一旦分开之后我们会减少很多重复的存储部门信息, 因为存在多个员工对应同一个部门的情形,虽然是多个员工,但是对应的是同样的部门信息,记录, 我们没有必要 重复存储这些部门信息呀, 所以才分表 (理解为什么分表)
多对多关系
案例:学生与课程的关系
关系:一个学生可以选修多门课程,一门课程也可以供多个学生选择
实现:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键
why 使用中间表,还是降低存储压力,减去不必要的存储,我们可以仅仅使用两列来关联左右两张表, 这样可以避免重复的数据存储 ( 保证数据唯一一份 )
全部从避免数据冗余,重复存储相同数据的角度去考虑外键, 中间表 分表的原因
连表查询 (集合运算)
数据准备
# 准备班级表 create table if not exists class ( `id` tinyint, `name` varchar(10) ) engine = innodb default charset = utf8mb4; insert into class values(1, '重点班'); insert into class values(2, '重点班'); insert into class values(3, '平行班'); insert into class values(4, '平行班'); # 准备学生表 create table if not exists student ( student_id tinyint primary key, name varchar(10), age tinyint, class_id tinyint ) engine = innodb default charset = utf8mb4; insert into student values(10, '张三', 19, 3); insert into student values(11, '李四', 20, 2); insert into student values(12, '王五', 22, 1); insert into student values(13, '赵四', 27, 4); insert into student values(14, '麻子', null, 3);
笛卡尔积 (A * B)
- 笛卡尔集合运算 A 1, 2, 3 B 4, 5, 6 A✖B (1, 4), (1, 5), (1, 6), (2, 4), (2, 5), (2, 6), (3, 4), (3, 5), (3, 6);
- select * from A, B;
- select * from A inner join B;
- 求取 class 表 和 student表的笛卡尔积喔
select * from class, student; +------+-----------+------------+--------+------+----------+ | id | name | student_id | name | age | class_id | +------+-----------+------------+--------+------+----------+ | 1 | 重点班 | 10 | 张三 | 19 | 3 | | 2 | 重点班 | 10 | 张三 | 19 | 3 | | 3 | 平行班 | 10 | 张三 | 19 | 3 | | 4 | 平行班 | 10 | 张三 | 19 | 3 | | 1 | 重点班 | 11 | 李四 | 20 | 2 | | 2 | 重点班 | 11 | 李四 | 20 | 2 | | 3 | 平行班 | 11 | 李四 | 20 | 2 | | 4 | 平行班 | 11 | 李四 | 20 | 2 | | 1 | 重点班 | 12 | 王五 | 22 | 1 | | 2 | 重点班 | 12 | 王五 | 22 | 1 | | 3 | 平行班 | 12 | 王五 | 22 | 1 | | 4 | 平行班 | 12 | 王五 | 22 | 1 | | 1 | 重点班 | 13 | 赵四 | 27 | 4 | | 2 | 重点班 | 13 | 赵四 | 27 | 4 | | 3 | 平行班 | 13 | 赵四 | 27 | 4 | | 4 | 平行班 | 13 | 赵四 | 27 | 4 | | 1 | 重点班 | 14 | 麻子 | NULL | 3 | | 2 | 重点班 | 14 | 麻子 | NULL | 3 | | 3 | 平行班 | 14 | 麻子 | NULL | 3 | | 4 | 平行班 | 14 | 麻子 | NULL | 3 | +------+-----------+------------+--------+------+----------+ 20 rows in set (0.00 sec)
- 上述存在问题,存在很多错误的记录数据 合并显示 , 这些就是 id 和 class_id 不相等的部分, class的 id 和 student的 class_id 无法对应起来的记录
- 条件约束,消除无效记录
select * from class c, student s where c.id = s.class_id; +------+-----------+------------+--------+------+----------+ | id | name | student_id | name | age | class_id | +------+-----------+------------+--------+------+----------+ | 3 | 平行班 | 10 | 张三 | 19 | 3 | | 2 | 重点班 | 11 | 李四 | 20 | 2 | | 1 | 重点班 | 12 | 王五 | 22 | 1 | | 4 | 平行班 | 13 | 赵四 | 27 | 4 | | 3 | 平行班 | 14 | 麻子 | NULL | 3 | +------+-----------+------------+--------+------+----------+
inner join on
只取两张表有对应关系的记录
- select * from A, B where 条件限制(两表关系部分)
select * from class c, student s where c.id = s.class_id;
- select * from A inner join B on 条件限制(两表关系部分)
select * from class c inner join student s on c.id = s.class_id; +------+-----------+------------+--------+------+----------+ | id | name | student_id | name | age | class_id | +------+-----------+------------+--------+------+----------+ | 3 | 平行班 | 10 | 张三 | 19 | 3 | | 2 | 重点班 | 11 | 李四 | 20 | 2 | | 1 | 重点班 | 12 | 王五 | 22 | 1 | | 4 | 平行班 | 13 | 赵四 | 27 | 4 | | 3 | 平行班 | 14 | 麻子 | NULL | 3 | +------+-----------+------------+--------+------+----------+ 5 rows in set (0.00 sec)
left outer join on
在内连接的基础上保留左表没有对应关系的记录
- select * from A left outer join B on 条件限制(两表关系部分)
- 为了方便测试: 我在左表中增添没有对应关系的两条记录:
- 记录1: 5 平行班
- 记录2: 6 平民班
insert into class values(5, '平行班'); insert into class values(6, '平民班');
select * from class c left outer join student s on c.id = s.class_id; +------+-----------+------------+--------+------+----------+ | id | name | student_id | name | age | class_id | +------+-----------+------------+--------+------+----------+ | 3 | 平行班 | 10 | 张三 | 19 | 3 | | 2 | 重点班 | 11 | 李四 | 20 | 2 | | 1 | 重点班 | 12 | 王五 | 22 | 1 | | 4 | 平行班 | 13 | 赵四 | 27 | 4 | | 3 | 平行班 | 14 | 麻子 | NULL | 3 | | 5 | 平行班 | NULL | NULL | NULL | NULL | | 6 | 平民班 | NULL | NULL | NULL | NULL | +------+-----------+------------+--------+------+----------+ 7 rows in set (0.00 sec) select * from class c inner join student s on c.id = s.class_id; +------+-----------+------------+--------+------+----------+ | id | name | student_id | name | age | class_id | +------+-----------+------------+--------+------+----------+ | 1 | 重点班 | 12 | 王五 | 22 | 1 | | 2 | 重点班 | 11 | 李四 | 20 | 2 | | 3 | 平行班 | 10 | 张三 | 19 | 3 | | 3 | 平行班 | 14 | 麻子 | NULL | 3 | | 4 | 平行班 | 13 | 赵四 | 27 | 4 | +------+-----------+------------+--------+------+----------+ 5 rows in set (0.00 sec) # 对比一下发现, 左外连接就是整个左表记录完全显示 # 就算没有关联记录也显示
right outer join on
在内连接的基础上保留右表没有对应关系的记录
- select * from A right outer join B on 条件限制(两表关系部分)
- 为了方便测试: 我在左表中增添没有对应关系的两条记录:
- 记录1:15 王顺 25 null; 没有对应class_id
- 记录2: 16 虹猫 30 null; 没有对应class_id
insert into student values(15, '赵顺', 25, null); insert into student values(16, '虹猫', 30, null);
select * from class c right outer join student s on c.id = s.class_id; +------+-----------+------------+--------+------+----------+ | id | name | student_id | name | age | class_id | +------+-----------+------------+--------+------+----------+ | 1 | 重点班 | 12 | 王五 | 22 | 1 | | 2 | 重点班 | 11 | 李四 | 20 | 2 | | 3 | 平行班 | 10 | 张三 | 19 | 3 | | 3 | 平行班 | 14 | 麻子 | NULL | 3 | | 4 | 平行班 | 13 | 赵四 | 27 | 4 | | NULL | NULL | 15 | 赵顺 | 25 | NULL | | NULL | NULL | 16 | 虹猫 | 30 | NULL | +------+-----------+------------+--------+------+----------+ 7 rows in set (0.00 sec) select * from class c inner join student s on c.id = s.class_id; +------+-----------+------------+--------+------+----------+ | id | name | student_id | name | age | class_id | +------+-----------+------------+--------+------+----------+ | 3 | 平行班 | 10 | 张三 | 19 | 3 | | 2 | 重点班 | 11 | 李四 | 20 | 2 | | 1 | 重点班 | 12 | 王五 | 22 | 1 | | 4 | 平行班 | 13 | 赵四 | 27 | 4 | | 3 | 平行班 | 14 | 麻子 | NULL | 3 | +------+-----------+------------+--------+------+----------+ 5 rows in set (0.00 sec) # 对比之下right outer join 就是相关记录 # + 右表多余不相关记录全部显示右表
子查询 (嵌套查询,合并查询)
⼀条select语句结果作为另⼀条select语法⼀部分(查询条件,查询结果,表等)。
- select ....查询字段 ... from ... 表.. where ... 查询条件
- eg : 查询 class表中 张三所在班级的总人数.
- 分步1: 查询张三所在班级
- 分布2: 根据第一步获得的班级查询这个班级的总人数
# 第一步获取张三所在班级 select class_id from student where name = '张三'; +----------+ | class_id | +----------+ | 3 | +----------+ 1 row in set (0.00 sec) # 第二步获取张三班级的总人数 select c.id '班级', count(*) '学生人数' from class c, student s where c.id = s.class_id && c.id = 3 group by c.id; +--------+--------------+ | 班级 | 学生人数 | +--------+--------------+ | 3 | 2 | +--------+--------------+ 1 row in set (0.00 sec)
- 合并上述查询方式:
- 合并方式1: 将 select 查询班级的结果作为条件
- select ....查询字段 ... from ... 表.. where 条件1 && c.id = (select ...) group by...
select c.id '班级', count(*) '学生人数' from class c, student s where c.id = s.class_id && c.id = (select class_id from student s where s.name = '张三') # select 查询结果当作条件进行子查询, 嵌套查询 group by c.id;
- 合并方式2:将select 查询班级的结果作为一个新的表进行内连接组合查询
- select 查询字段 from 表1 (select ...)表2 where ...
- 从一次select 之后的结果当作表进行内联查询
select c.id '班级', count(*) '学生人数' from class c, (select * from student stu where stu.name = '张三') s # 对于select name = 张三的查询结果别名s 当作新表联合查询 where c.id = s.class_id group by c.id; # 上述是一种子查询的方式,但是并不能获取张三班级人数 # 仅作为一种查询方式而已
- 总之,将来我们进入公司需要操作的表结构绝对是多表操作,多表查询的时候,多表需要联合查询
- 内连接 inner join left outer join right outer join 都是不同的连接多表的方式
- 子查询, 我们需要 思考的是 一条select查询的结果可以做另外一条select 查询的条件或者是 子表 (select ...) 作为一个where 条件 或者是一个新的 table 在做一次查询
- 算是嵌套select 查询
总结
- 小杰从最简单的SQL语法和表操作入手,从 DDL(表定义,表结构操作) DQL (select表查询) DML (记录的增删改查)着手,总结了近期所学,也算是sql入门的一个精简资料了...
- 熟练的掌握理解了上述这些东西,就大致可以算是会操作数据表了
- 多表查询 将两个表以不同的方式各种形式连接起来联合查询,我们需要不断地积累,根据不同地实际业务场景采取不同地多表查询连接方式
- 灵活地嵌套使用 select 语句, 一条select 查询结果可以做条件 还可以做 表进行内连接查询.
- 应老规矩:简要地总结上述关键字操作
DDL回忆
- use 数据库; 指定数据库操作
- show databases; 显示所有数据库 show tables; 显示所有数据表
- create table 表名; create database 库名; 新建表 + 库 不安全 加上 if not exists 修饰可避免报错
- drop table 表名; drop database 库名; 删除表 + 库 不安全 加上 if exists 修饰可避免报错
- alter table 表名 add/modify/change 列调整; 可以修改表结构
- alter table 表名 drop 列名; 删除列
DML回忆
- insert into 表名(指定字段) values(指定值); 增加记录
- delete from 表名 where 指定删除记录; 删除记录
- update 表名 set 字段 = 值 where 指定跟新记录; 跟新记录
DQL回忆
- select 指定列 from 表; (指定列查询)
- select distinct 列 from 表; 去重查询列
- select * from 表 order by 指定排序列 排序规则
- select 指定分组字段 聚合函数 from 表 where ... group by 分组字段
- 聚合函数是为了在分组去重之后压缩多条记录为一条.
- select * from 表 limit 起始记录数, 每页记录数; 分页查询
- inner join 内连接 left outer join right outer join 外连接, 关系部分 + 其中一表独有部分
- select 查询结果可做条件 + 可以新表再 select 查询 (子查询)
mysql开发必知必会
mysql的数据库的数据库,即存储mysql数据库的底层目录,是在/var/lib/mysql目录下(Linux,win在目录下的data中)。
我们新创建的数据库db1就是在/var/lib/mysql目录下创建了一个文件夹db1。
mysql相关的目录
我们使用/usr/bin/mysqladmin创建过root用户的密码等操作。
mysql默认的配置文件读取顺序为:
[root@localhost bin]# which mysqld /usr/sbin/mysqld [root@localhost bin]# ^C [root@localhost bin]# /usr/sbin/mysqld --verbose --help |grep -A 1 \'Default options\' 2018-03-07 17:30:00 0 [Note] /usr/sbin/mysqld (mysqld 5.6.39) starting as process 14259 ... 2018-03-07 17:30:00 14259 [Note] Plugin \'FEDERATED\' is disabled. Default options are read from the following files in the given order: /etc/my.cnf /etc/mysql/my.cnf /usr/etc/my.cnf ~/.my.cnf #mysql的配置信息的读取目录顺序 2018-03-07 17:30:00 14259 [Note] Binlog end 2018-03-07 17:30:00 14259 [Note] Shutting down plugin \'CSV\' 2018-03-07 17:30:00 14259 [Note] Shutting down plugin \'MyISAM\'
查看mysql的字符集
mysql> show variables like \'character%\'; +--------------------------+----------------------------+ | Variable_name | Value | +--------------------------+----------------------------+ | character_set_client | utf8 | | character_set_connection | utf8 | | character_set_database | latin1 | | character_set_filesystem | binary | | character_set_results | utf8 | | character_set_server | latin1 | | character_set_system | utf8 | | character_sets_dir | /usr/share/mysql/charsets/ | +--------------------------+----------------------------+ 8 rows in set (0.00 sec) mysql> show variables like \'%char%\'; +--------------------------+----------------------------+ | Variable_name | Value | +--------------------------+----------------------------+ | character_set_client | utf8 | | character_set_connection | utf8 | | character_set_database | latin1 | | character_set_filesystem | binary | | character_set_results | utf8 | | character_set_server | latin1 | | character_set_system | utf8 | | character_sets_dir | /usr/share/mysql/charsets/ | +--------------------------+----------------------------+ 8 rows in set (0.00 sec)
此时数据库的字符集为拉丁,我们需要改为utf8。
修改mysql的默认配置文件/etc/my.cnf。
[mysql] # 设置mysql客户端默认字符集 default-character-set=utf8 socket=/var/lib/mysql/mysql.sock [mysqld] skip-name-resolve #设置3306端口 port = 3306 socket=/var/lib/mysql/mysql.sock # 设置mysql的安装目录 basedir=/usr/local/mysql # 设置mysql数据库的数据的存放目录 datadir=/usr/local/mysql/data # 允许最大连接数 max_connections=200 # 服务端使用的字符集默认为8比特编码的latin1字符集 character-set-server=utf8 # 创建新表时将使用的默认存储引擎 default-storage-engine=INNODB lower_case_table_name=1 max_allowed_packet=16M
关闭mysql并重新开启:
[root@localhost mysql-community-server-5.6.39]# service mysqld stop Stopping mysqld (via systemctl): [ 确定 ] [root@localhost mysql-community-server-5.6.39]# service mysqld start Starting mysqld (via systemctl): [ 确定 ] [root@localhost mysql-community-server-5.6.39]# mysql -uroot -p Enter password:
新建数据库db2显示db1与db2的数据库的字符编码:
mysql> create database db2; Query OK, 1 row affected (0.04 sec) mysql> use db2; Database changed mysql> show variables like \'%char%\'; +--------------------------+----------------------------+ | Variable_name | Value | +--------------------------+----------------------------+ | character_set_client | utf8 | | character_set_connection | utf8 | | character_set_database | utf8 | | character_set_filesystem | binary | | character_set_results | utf8 | | character_set_server | utf8 | | character_set_system | utf8 | | character_sets_dir | /usr/share/mysql/charsets/ | +--------------------------+----------------------------+ 8 rows in set (0.00 sec) mysql> use db1; Database changed mysql> show variables like \'%char%\'; +--------------------------+----------------------------+ | Variable_name | Value | +--------------------------+----------------------------+ | character_set_client | utf8 | | character_set_connection | utf8 | | character_set_database | latin1 | | character_set_filesystem | binary | | character_set_results | utf8 | | character_set_server | utf8 | | character_set_system | utf8 | | character_sets_dir | /usr/share/mysql/charsets/ | +--------------------------+----------------------------+ 8 rows in set (0.00 sec)
之前创建的数据库的字符编码仍为Latin,而修改过后的新建数据库已经使用utf8了,说明我们的配置生效了。
查看mysql的安装目录
[root@localhost mysql-community-server-5.6.39]# vim /etc/my.cnf [root@localhost mysql-community-server-5.6.39]# ps -ef|grep mysql root 14599 1 0 18:44 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe --datadir=/var/lib/mysql --socket=/var/lib/mysql/mysql.sock --pid-file=/var/run/mysqld/mysqld.pid --basedir=/usr --user=mysql mysql 14941 14599 0 18:44 ? 00:00:02 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mysqld.log --pid-file=/var/run/mysqld/mysqld.pid --socket=/var/lib/mysql/mysql.sock --port=3306 root 15008 11441 0 18:54 pts/0 00:00:00 grep --color=auto mysql
mysql主要的配置文件
二进制日志log-bin 用于主从复制;
错误日志log-error 默认关闭,记录严重警告和错误信息。
查询日志log 默认关闭,用来查询sql语句,开启会降低mysql整体性能
数据文件:前面提到的数据库的数据库文件,frm文件用于存放表结构,myd文件存放表数据,myi文件用于存放表索引。
配置文件:Linux为/etc/my.cnf,win为my.ini。
mysql架构介绍
第一层为连接层,connectors。
接下来就进入mysql层:
第二层为一部分是与外层的连接池(并发部分),备份安全集群等部份,然后到对输入sql语句进行解析的部分(sqlinterface接口对你要进行是curd的某项操作进行分开解析),解析到optimizer部分为mysql自己的逻辑操作部分(人进行数据查询首先关注select部分,但是机器关心的是从from哪张表开始,这个部分是mysql的优化器),最后一部分是缓存与缓冲,解决数据库存取时的io操作问题,所以这一层主要是业务逻辑处理层。
第三层为可拔插的存储引擎部分。有很多种,我们经常使用到的是innodb有时也用myisam。
第四层就是底层的文件存储层,底层的文件系统等。
与其他数据库相比,mysql的架构可以在多种不同场景应用并发挥良好作用主要体现在存储引擎的架构上,可插拔的存储引擎架构将查询处理和其他的系统任务以及数据的存储提取相分离,我们可以根据业务的实际需求选择合适的存储引擎。
查看存储引擎:
mysql> show engines; +--------------------+---------+----------------------------------------------------------------+--------------+------+------------+ | Engine | Support | Comment | Transactions | XA | Savepoints | +--------------------+---------+----------------------------------------------------------------+--------------+------+------------+ | InnoDB | DEFAULT | Supports transactions, row-level locking, and foreign keys | YES | YES | YES | | CSV | YES | CSV storage engine | NO | NO | NO | | MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO | | BLACKHOLE | YES | /dev/null storage engine (anything you write to it disappears) | NO | NO | NO | | MyISAM | YES | MyISAM storage engine | NO | NO | NO | | MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO | | ARCHIVE | YES | Archive storage engine | NO | NO | NO | | FEDERATED | NO | Federated MySQL storage engine | NULL | NULL | NULL | | PERFORMANCE_SCHEMA | YES | Performance Schema | NO | NO | NO | +--------------------+---------+----------------------------------------------------------------+--------------+------+------------+ 9 rows in set (0.00 sec)
主流的两种存储引擎的对比:
关于mysql性能下降的分析
如果mysql的性能下降,sql慢,执行时间长,等待时间长,那么问题可能出现在:
1.查询语句写得太烂了
2.索引失效了
3.关联查询的join写的太多了(可能是表的设计的问题)
4.服务器调优及各个参数的设置问题(缓冲及线程数等)
你写的sql:
mysql都出来的查询语句
机读的顺序
mysql索引
定义:索引是帮助mysql高效获取数据的数据结构。我们可以根据索引的特点理解为,索引就是排好序的快速查找的数据结构。
以后别人问你mysql索引是什么的时候,别再说什么字典什么查的快什么的了,你得专业的告诉他,索引就是用B+树实现的快速查找的一种数据类型。
索引影响的部分是where后面的查找与orderby后面的排序。
除了数据,数据库系统还维护了一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。
为了加快col2的查找,可以维护一个二叉树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定复杂度内获取到相应数据,从而快速的检索出符合条件的记录。
一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。
我们一般说的索引都是b树结构组织索引,其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引,唯一索引都是使用b树索引。除了b树索引意外还有hash索引。
索引的优势:
查找时提高数据检索效率,降低数据库io成本。排序时,索引对数据进行排序,降低数据排序成本,降低了CPU的消耗。
索引的劣势:
索引本身是一张表,保存了主键与索引字段,并指向实体表的记录,所以索引本身也占空间。
索引虽然大大加快了查询速度,却降低了更新速度,在更新表时,不仅要存更新的数据,还要存索引文件每次更新添加的索引列字段,都会调整因为更=新带来的简直变化后的索引信息。
索引只是提高效率的一个因素,如果你的mysql有大量数据的表,那么就要花时间研究建立最优秀的索引或优化查询语句。
mysql索引分类
单值索引:一个索引只包含单个列,一个表可以有多个单列索引。
唯一索引:索引列的值必须唯一,但允许空值。
复合索引:一个索引包含多个列。
b树查找
这里磁盘块Ⅰ存储了指引搜索方向的数据项,17与35不一定存在于数据表中,只是一个趋势,叶子节点存的数据必须是真实存在的数据。根节点的数据表示,查找的数据范围是小于17还是位于17和35之间还是大于35的数据。然后从根出发一直找到索引的数据,只需要三步即可。
那些情况需要创建索引
1.主键自动建立唯一索引
2.频繁作为查找条件的字段应该创建索引
3.查询中与其他表关联的字段,外键关系建立索引
4.频繁更新的字段不适合创建索引,不只是更新记录还会更新索引,加重io负担
5.where条件里用不到的字段不创建索引
6.单键/组合索引的选择问题,在高并发下倾向创建组合索引
7.查询中排序的字段,排序字段通过索引访问将大大提高排序速度
8.查询中统计或者分组字段
哪些情况不要创建索引
1.表记录太少
2.经常增删改的表
3.数据重复且分布平均的表的字段。比如性别列。
Explain
除了mysql的optimizer自动的优化导致的sql性能下降和cache与buffer的硬件部分性能瓶颈,我们分析一条sql语句是否高效就必须用到Explain进行查询。
explain关键字可以模拟优化sql查询语句,从而知道mysql是如何处理你的sql语句的,可以用来分析你的查询语句或是表结构的性能瓶颈。
能查看什么:
表的读取顺序,数据读取操作的操作类型,那些索引可以使用,那些索引被实际使用,表之间的引用,每张表有多少行被优化器查询。
执行方法
explain+sql
explain中的字段
id字段
此字段用来表示mysql查询时执行select子句或操作表的顺序,即表的读取顺序。
查询结果可能有三种情况
1.id相同时。
即此时mysql加载的顺序为t1>t3>t2。
2.id不同
即此时mysql加载的顺序为t3>t1>t2。
3.混合存在
即此时mysql加载的顺序为t3><derived2>>t2。
select_type字段
即数据读取操作的操作类型
1.simple
简单的select查询,查询不包括子查询与union
2.primary
如果查询包含了任何复杂的字部分,最外层查询被标记为primary
3.subquery
在select或者where列表中包含了子查询
4.derived
在from列表包含的子查询被标记为derived衍生,mysql会递归执行这些子查询,把结果放到临时表中
5.union
若第二个select出现在union后则被标记为union,若union包含在from子句的子查询中,外层select将被标记为derived。
6.union result
从union表获取结果的select
table字段,就是表名字段,表时这一行数据是关于那张表的。
type字段
直接的显示了表是否被优化是否最佳状态的直接体现。
一般常见的类型为system>const>eq_ref>ref>range>index>all
当你的数据达到百万级并且sql语句执行为all,那么你的查询语句或者说表需要进行优化了。当你的sql执行为ref或者range就已经很优了。
type的不同状态:
1.system
表只有一行记录(等于系统表),是const类型的特例,平时基本遇不到可以忽略不计
2.const常量
表通过索引一次就找到了,const用于比较primary key或者unique索引,因为只匹配一行数据,所以很快
这里的id为主键索引,在t1表唯一只匹配一行数据,查找相当于查询一个常量。
一张表只有一行数据,相当于京东就你一个用户,快是快,但是实际基本不会用到。
3.eq_ref
唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描
类比员工表与部门表中,部门表id最为外键,员工表的外键关联到部门表id,将部门表id建成索引,ceo作为公司员工,在部门表索引时只能查到一个唯一的数据与之匹配,这种情况就是eq_ref,反之其他的部门拥有多名员工,查询就是ref。
4.ref字段
非唯一索引扫描,匹配某个单独值的所有行,可能找到符合条件的多个值,所以它是查找和扫描的混合体。
类比上述的表查找所有的程序员就是ref类型。
5.range
只检索给定范围行,使用一个索引来选择行,key列显示你使用了哪个索引,一般是where语句出现between,<,>,in等的查询,这种范围扫描优于全标搜索,因为他有起始位置。
6.index
全索引扫描,优于all因为只扫描了索引,索引往往小于数据的大小。
7.all
全表查询,匹配到所有行
possible_keys与key字段
possible_key是smysql判断你可能使用的索引,显示可能应用在这张表的索引,一个或多个,查询涉及到的字段上若存在索引,该索引将被列出,但不一定被查询实际使用
key字段
实际中使用的索引,如果为null则没有使用索引
这种情况为查询中使用了覆盖索引,该索引与查询的select重叠。
key_length字段
条件越精确,key_length越长,此字段为索引字段最大可能长度,并非实际使用长度,根据定义计算得出,不是通过表内检索得来的值。表示索引中使用的字节数,可通过该列计算查询中使用的索引长度。
ref字段
显示索引的那一列被使用了,如果可能的话是一个常数,那些列或常量被用于查找索引列上的值。
即ref体现了实际被索引的列。
rows字段
根据表中信息,估算找到所需记录大致需要读取的行数。
extra
包含的额外信息,也是重要字段
1.using filesort
说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。
这里我们对表中的三列进行了联合索引,前者在orderby排序时使用了外部排序,这大大降低了表的排序性能,因为在使用索引排序时最好是按照它的索引顺序进行排序。
2.using temporary
使用了临时表保存中间结果,mysql在对查询结果排序时使用了临时表,常见于排序orderby和分组查询groupby。
在这张表中,创建col1与col2联合索引,groupby跳过col1直接groupby col2,导致断层,需要创建临时的表,mysql对临时表进行一次自己的排序。
3.using index
所以这是个好字段,使用覆盖索引。
还有一些其他字段不很重要的字段,就不一一解释了,有兴趣自行了解。
范围后的索引会失效
左右连接查表的时候给另一边加索引。
多表连接查询时的优化:
1.减少join查询的循环总次数,即永远以小结果集驱动大结果集。
2.优先优化nestedloop的内层循环。
3.保证join语句中被驱动表join字段已经被索引
4.无法保证被驱动表的join条件字段被索引且内存资源充足的前提下,不要吝惜joinbuffer的设置。
如何避免索引失效
1.全值匹配我最爱
此为完全覆盖和部分覆盖,并且满足左前缀法则,索引正确使用。
2.最佳左前缀法则
带头大哥不能死,中间兄弟不能断。
3.不在索引列上做任何操作(计算函数类型转换)、会导致索引失效而转向全表扫描。
索引列上少计算。
4.存储引擎不能使用索引范围条件右边的列
后一条的key_length长度比之前完全覆盖短,age后索引其实已经失效了,即范围之后全失效。
5.尽量使用覆盖索引
即覆盖列与索引列一致,减少select *的使用
6.使用不等于时无法使用索引会导致全表扫描
7.isnull与isnotnull也无法使用索引
8.like以通配符开头,mysql索引失效变成全表扫描。
如果需求一定要两边都是百分号查找,那么需用使用覆盖索引来解决索引失效问题。
9.字符串不加单引号索引失效
10.少用or,用它来连接时会索引失效
这十条mysql索引优化的口诀是:
带头大哥不能死,中间兄弟不能断。
索引列上无计算,like百分加右边。
字符串里有引号。
mysql的自动调优,因为上述的都是常量级别,所以会被调成覆盖索引类似。
此表1,2是覆盖索引,3是范围索引,所以用到三个索引,因为3type降为range等级,范围之后全失效,所以4未索引。
同理,此处使用了四个索引。
此处,1,2为查找索引,3用于排序索引,所以,严格地认为此处使用了三个索引。
同理,与上述一致,完全一样。
c4空中阁楼,中间兄弟断了,mysql自己优化排序了,糟糕。
1查找索引,23为排序索引。
与上述不同,此处的2,3没有按照索引顺序排列,违背左前缀优先的理念,故出现filesort。
使用两个查找索引和两个排序索引。
上面c2被查询了,所以排序时相当于一个常量,所以并为filesort。
分组之前必排序,所以他与orderby基本一致。
熟记口诀:
数据库分析
1.先观察一下sql生产时的慢sql情况
2.开启慢日志查询,设置阙值,查询过慢的sql挑出来
3.explain+sql分析
4.show profile
5.对sql数据库服务器进行参数调优
总结就是:开启捕获慢查询语句,explain分析定位,show profile查询sql在MySQL服务器里执行细节与生命周期,sql数据库服务器调优。
in与exist的转换。
show profile
查看是否开启了,默认是关闭的
mysql> show variables like \'profiling\'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | profiling | OFF | +---------------+-------+ 1 row in set (0.01 sec)
所以我们需要打开
mysql> set profiling=on; Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> show variables like \'profiling\'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | profiling | ON | +---------------+-------+ 1 row in set (0.00 sec)
show profile cpu,block io for query (id);
看出一条sql的完整生命周期。
比较严重的问题:
converting head to myisam 查询结果太大,内存不够用存到磁盘上了。
creating tmp table 创建临时表(拷贝数据到临时表,用完删除)
copying to tmp table on disk 把内存中临时表复制到磁盘,
locked 锁住了
以上是关于MYSQL必知必会,详尽入门,一文帮你学会SQL必知必会的主要内容,如果未能解决你的问题,请参考以下文章