昨天 Poppy 问我是不是应该学一些网页开发的东西, 我的回答是这样的:
今天花了点时间汇总了一些 MySQL 简单的命令.
======== 正文分割线 ========
有哪些常见的数据库:
- Oracle
- MySQL
- SQL Server
- MongoDB
- Redis
- Hadoop
以下所有命令基于数据库 kis_blog
编写, 可以通过 Navicat 或者 DataGrip 亲自体验一下.
注:
- poppython.com 的数据库采用的是阿里云的 RDS 云数据库(MySQL);
- Navicat 和 DataGrip 是 MySQL 的 GUI 客户端;
- MySQL 搭建完毕以后可以创建多个数据库, 每个数据库可以创建多张数据表;
- 警号
#
开头的是注释; - 分号
;
结尾代表语句结束; - 本文由简入繁的顺序编写, 建议从头阅读, 不要跳过;
blogs
表结构
kis_blog
有 5 张表, 分别是:
-
users
: 用户表 -
categories
: 分类表 -
comments
: 评论表 -
tags
: 标签表 -
blogs
: 文章表
MySQL 的数据表类似于 Excel, 下边是表 blogs
的 Excel 描述:
上图中蓝色的文字是列, 橙色的文字是列的注释, 黑色的文字是数据, 每一行代表一条数据.
以下章节按照 crud boy
的顺序编写:
CURD 代表 CREATE(创建)、RETRIEVE(检索)、UPDATE(更新)、DELETE(删除) 几个单词的首字母
查
查询数据使用 SELECT
关键字:
# 查 blogs 的所有列
SELECT * FROM blogs;
# MySQL 的关键字不区分大小写, 下面两条语句是等价的(建议使用大写):
SELECT * FROM blogs;
select * from blogs;
# 查 blogs 的单个列
SELECT id FROM blogs;
# 查 blogs 的多个列
SELECT id, title FROM blogs;
# 去重
SELECT DISTINCT category_id FROM blogs;
排序使用 ORDER BY
关键字:
# 按照 id 排序(默认升序)
SELECT * FROM blogs ORDER BY id;
# 按照 id 升序
SELECT * FROM blogs ORDER BY id ASC;
# 按照 id 降序
SELECT * FROM blogs ORDER BY id DESC;
# 按照 id, category_id 多个列排序(默认升序)
SELECT * FROM blogs ORDER BY user_id, category_id;
# 按照 id, category_id 多个列排序(指定排序方向)
# ASC 和 DESC 只能影响到其前面的列
SELECT * FROM blogs ORDER BY user_id ASC, category_id DESC;
过滤数据使用 WHERE
关键字:
# 只看 poppy(你的 id 是 2 ) 发布的文章
SELECT * FROM blogs WHERE user_id = 2;
# 只看置顶状态的文章
# status 列存储了一个字符串有以下可选值: DRAFT(草稿)、已发布(PUBLISHED)、TOP(置顶)、HIDE(隐藏)
SELECT * FROM blogs WHERE status = 'PUBLISHED';
# 只看 id 大于 10 的文章
SELECT * FROM blogs WHERE id > 10;
# 只看 id 小于等于 2 的文章
SELECT * FROM blogs WHERE id <= 2;
# 只看 id 位于 2 和 10 之间的文章 (包括 2 也包括 10)
SELECT * FROM blogs WHERE id BETWEEN 2 AND 10;
# 只看 id 不等于 6 的文章, 以下两条语句是等价的
SELECT * FROM blogs WHERE id <> 6;
SELECT * FROM blogs WHERE id != 6;
过滤数据除了 WHERE
关键字之外, 还有 AND
、OR
、IN
、NOT
等子句可以使用:
# 查看 user_id 为 1 并且 category_id 为 2 的文章
SELECT * FROM blogs WHERE user_id = 1 AND category_id = 2;
# 查看 category_id 为 2 或者 category_id 为 3 的文章
SELECT * FROM blogs WHERE category_id = 2 OR category_id = 3;
# 猜猜这行什么意思?
SELECT * FROM blogs WHERE (category_id = 2 OR category_id = 3) AND user_id = 1;
# 查看 category_id 为 2 或者 category_id 为 3 的文章
SELECT * FROM blogs WHERE category_id IN (2, 3);
# 只看 summary 为空的文章
SELECT * FROM blogs WHERE summary IS NULL;
# 只看 summary 不为空的文章
SELECT * FROM blogs WHERE summary IS NOT NULL;
在一些后台系统中, 经常需要分页查询数据, 假如前端传递 /api/blogs?current=2&size=10
表示页码为 2, 页长为 10, 后端需要做以下处理:
# 查询第 2*10 条数据之后的 10 条数据, 也就是第 21 条到第 30 条
SELECT * FROM blogs LIMIT 2 * 10, 10;
# 查询所有数据作为 count, 后端需要返回 current, size, count 三个字段给前端
# 用于发起下一次分页查询
SELECT COUNT(id) FROM blogs;
以上查询数据的方式都是通过精确匹配, MySQL 同样支持模糊匹配.
通配查询使用 LIKE
关键字, 最常用的通配符的 %
, %
表示任意字符出现任意次数.
# 查看 title 中以 Git 开头的文章
SELECT * FROM blogs WHERE title LIKE 'Git%';
# 查看 title 中以 Git 结尾的文章
SELECT * FROM blogs WHERE title LIKE '%Git';
# 查看 title 包含 Git
SELECT * FROM blogs WHERE title LIKE '%Git%';
通过以上查询方式得到的数据就像 excel 里的行, 还可以做简单的拼接、计算:
# 给标题加上书名号
# CONCAT 函数表示将参数拼接在一起
# AS 关键字表示给拼接后的列取个新名字叫 full_title
SELECT CONCAT('《', title, '》') AS full_title FROM blogs;
# 去掉 title 的首空格
SELECT LTRIM(title) FROM blogs;
# 去掉 title 的尾空格
SELECT LTRIM(title) FROM blogs;
# 去掉 title 的首尾空格
SELECT TRIM(title) FROM blogs;
# 计算点赞率 (点赞率 = 点赞数 / 浏览数)
SELECT like_count / read_count AS like_rate FROM blogs;
MySQL 还支持以下函数:
- 文本处理类:
-
LEFT()
: 返回字符串左边的字符 -
LENGTH()
: 返回字符串的长度 -
LOWER()
: 将字符串转换为小写 -
LTRIM()
: 去掉字符串左边的空格 -
RIGHT()
: 返回字符串右边的字符 -
RTRIM()
: 去掉字符串右边的空格 -
SOUNDEX()
: 返回字符串的SOUNDEX值 -
UPPER()
: 将字符串转换为大写
- 日期处理类:
-
YEAR()
: 获取年 -
MOUNTH()
: 获取月 -
DAY()
: 获取日 -
NOW()
: 获取当前日期 - 还有其他很多函数...
- 数值处理类:
-
AVT()
: 返回某列的平均值 -
COUNT()
: 返回某列的行数 -
MAX()
: 返回某列的最大值 -
MIN()
: 返回某列的最小值 -
SUM()
: 返回某列值之和 -
ABS()
: 返回一个数的绝对值 -
COS()
: 返回一个角度的余弦 -
EXP()
: 返回一个数的指数值 -
PI()
: 返回圆周率 -
SIN()
: 返回一个角度的正弦 -
SQRT()
: 返回一个数的平方根 -
TAN()
: 返回一个角度的正切 - 还有其他很多函数...
除了上述的各种单表查询方法外, MySQL 还支持各种姿势的联表查询 (SQL 最强大功能之一):
-
INNER JOIN
(内联接) OUTER JOIN
(外联接)
-
LEFT JOIN
(左联接) -
LEFT OUTER JOIN
(左外联接) -
RIGHT JOIN
(右联接) -
RIGHT OUTER JOIN
(右外联接) -
FULL JOIN
(全连接) -
FULL OUTER JOIN
(全外联接)
- 交叉联接
这部分内容略多, 可以参考 Mysql表连接查询.
增
创建数据库:
# 创建数据库
# 这行语句比较复杂, 可以简单这样理解:
# 1. 如果 kis_blog 已存在的话则跳过
# 2. 如果 kis_blog 不存在则创建, 创建时:
# + 指定字符集为 utf8
# + 指定字符序为 utf8_general_ci
CREATE DATABASE IF NOT EXISTS kis_blog DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
创建数据表 blogs
:
CREATE TABLE IF NOT EXISTS `blogs`(
`id` INT UNSIGNED AUTO_INCREMENT,
`title` VARCHAR(100) NOT NULL,
`summary` VARCHAR(200),
`content ` VARCHAR(10000),
PRIMARY KEY ( `id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
创建数据表的命令比较复杂, 以上仅定义了 id、title、summary、content 四个列.
数据表的每个列都有一个类型, MySQL 提供了很多可选的选项, 在实际开发中, 程序员需要按照真实的业务情况选择合适的数据类型, 简单来说分为以下几种:
- 字符串:
-
CHAR
: 定长字符串 -
VARCHAR
: 变长字符串 -
TINYTEXT
: 短文本, 最多存储 255 个字符 (2^8-1) -
TEXT
: 长文本, 最多存储 65535 个字符 (2^16-1) -
MEDIUMTEXT
: 中等长度文本, 最多存储 16777215 个字符 (2^24-1) -
LONGTEXT
: 极大文本, 最多存储 4294967295 个字符 (2^32-1) - 其他二进制类型...
- 数字
-
TINYINT
: 小整数, 0 - 255 之间 -
SMALLINT
: 大整数, 0 - 65535 之间 -
MEDIUMINT
: 大整数, 0 - 16777215 之间 -
INT
: 大整数, 0 - 4294967295 之间 -
BIGINT
: 极大整数 -
FLOAT
: 单精度浮点数 -
DOUBLE
: 双精度浮点数 -
DECIMAL
: 小数
- 日期
-
DATE
: 日期, 例如: 2013-07-20 (没错, 我们的纪念日) -
TIME
: 时间, 例如: 05:20:00 -
DATETIME
: 混合 DATE 和 TIME, 例如: 2013-07-20 05:20:00 -
YEAR
: 年, 例如: 2013 -
TIMESTAMP
: 时间戳
除了数据类型外, 定义数据表还需要一些约束字段:
-
AUTO_INCREMENT
: 表明这个列是自增的, 常用于 id, 会从 1 开始, 每加一条数据就自动加 1 作为新增数据的id; -
NOT NULL
: 表明这个列是必填的, 不能为空 -
UNIQE
: 表明这个列是唯一的 -
PRIMARY KEY
: 表明这个列是主键, 主键很重要
关于主键:
- 主键是一条记录的唯一表示, 就像公民的身份证号码;
- 主键字段必须唯一并且非空;
- 一个表只能有一个主键;
- 主键可以包含一个字段或多个字段, 多个字段时称为 "联合主键";
关于外键:
- 外键用来表示两个表之间的关系;
- 一个表可以有多个外键;
poppython.com 的数据库里有以下场景:
有用户表 users
用来存储用户的信息, 另有文章表 blogs
, 一个用户可以有多个文章, 这种情况下, users
和 orders
是一对多的关系.
此时可以将 users.id
字段作为 blogs
表的主键, users.id
就是 users
表的外键, users
通常被称为主表, blogs
被称为子表.
创建数据使用 INSERT
关键字:
# 创建一篇示例文章
INSERT INTO blogs (
user_id,
title,
category_id,
content,
pathname,
STATUS
)
VALUES (
2,
'示例标题',
7,
'示例文章',
'test-insert',
'DRAFT'
);
改
在实际的开发过程中, 数据表设计比较复杂, 需要尽可能的满足当前甚至未来的业务, 经常需要多个程序员共同商讨.
修改数据相对来说比较简单, 修改数据表使用 ALTER
关键字, 修改数据使用 UPDATE
关键字:
# 为 blogs 添加列
ALTER TABLE blogs ADD test_column CHAR(20);
# 删除 blogs 的 test_column 列
ALTER TABLE blogs DROP COLUMN test_column;
# 将 id 为 37 的文章状态改为 "置顶"
UPDATE status = 'TOP' FROM blogs WHERE id = 37;
# 将所有文章的状态改为 "已发布"
UPDATE status = 'PUBLISHED' FROM blogs WHERE id > 0;
删
删数据也很简单, 删除数据库和数据表使用 DROP
关键字, 删除数据使用 DELETE
关键字:
# 删除数据库
DROP DATABASE db_name;
# 删除数据表
DROP TABLE table_name;
# 删除 id 为 42 的文章
DELETE FROM blogs WHERE id = 42;
其他
事务
在实际的开发中经常需要批量执行一些 SQL 语句, 并且这批 SQL 要么全部成功, 要么不执行, 这种情况下需要使用事务.
START TRANSACTION
# 批量的 SQL 语句...
COMMIT TRANSACTION
存储过程
在我的理解中, 存储过程相当于封装了 SQL 的函数.
TODO: 本王这部分的了解太浅, 暂时略过.
游标
TODO: 本王这部分的了解太浅, 暂时略过.
撤销
SQL 的 ROLLBACK 命令用来撤销 SQL 语句.
DELETE FROM table_name;
ROLLBACK;
索引
索引用来排序数据以加快搜索的速度, 索引分为:
- 普通索引
- 唯一索引
- 主键索引
# 创建普通索引
ALTER TABLE blogs ADD INDEX title;
# 创建唯一索引
ALTER TABLE blogs ADD UNIQUE pathname;
# 创建主键索引
ALTER TABLE blogs ADD PRIMARY KEY(id);
查看索引:
SHOW INDEX FROM blogs;
删除索引:
DROP INDEX title ON blogs;
ORM
从上边的原始 SQL 代码可以看到操作数据表是很复杂的, 在真实的业务中经常需要在 SQL 中需要注入变量后拼装出冗长的 SQL 语句.
ORM 解决了以上问题, ORM 是 Object Relational Mapping (对象关系映射)的简称, 可以简单理解成操作 SQL 的第三方工具包.
poppython.com 中使用了基于 Node 的 Sequelize 作为 ORM, 下面罗列了一些代码片段.
下面的代码用来创建文章, 在前端使用 POST 请求访问 /api/v1/blog
接口时, 会携带参数走到以下方法中:
async create() {
const { ctx, service } = this;
const { title, pathname, summary, tagIds, categoryId, content, status } = ctx.request.body;
const userId = ctx.helper.getLoggedIdByToken(ctx.cookies.get("tk"));
try {
const existed = await service.blog.findOne({ where: { pathname } });
if (existed) {
ctx.body = { success: false, message: ctx.__("ExistMsg", pathname) };
return;
}
const created = await service.blog.create({
title,
pathname,
summary,
userId,
categoryId,
content,
status,
});
const tags = await service.tag.findAll({ where: { id: tagIds } });
await created.setTags(tags);
ctx.body = { success: true, message: ctx.__("SuccessSmg"), data: created.id };
} catch (e) {
ctx.logger.error("Error while BlogController.create, stack: ", e);
ctx.body = { success: false, message: ctx.__("InnerErrorMsg") };
}
}
上边的代码只有常规的对象, 完全看不到原始 SQL 的影子.
MySQL 的优化
在庞大的业务数据下, MySQL 经常需要各种各样的优化方式, 比如:
- 使用缓存数据库
- 使用恰当的数据类型
- 创建高性能的索引
- 优化查询语句
- 读写分离
- 分布式数据库
- ...
- 这方面我是半桶水, 就不晃荡了...
开发一个创建文章的功能的流程
第一步: 在后端定义了一些路由地址, 比如:
// 当用于访问 /admin/write.html 时, 返回一个静态的页面
router.get("/admin/write.html", controller.view.admin.renderWrite);
// 当收到 /api/v1/blog 的POST 请求时, 执行 `controller.api.blog` 控制器的 `create` 方法
router.post("/api/v1/blog", controller.api.blog.create);
第二步: 前端创建 write.html
, 下面是伪代码:
<form id="js_writeForm" action="#">
<input type="text" name="title" placeholder="请输入标题" />
<textarea name="content" placeholder="请输入正文"></textarea>
<button type="submit">提交</button>
</form>
<script>
$(document).ready(function () {
var $writeForm = $("#js_writeForm");
// 监听表单的提交事件
$writeForm.on("submit", function (event) {
// 阻止默认的提交事件
evt.preventDefault();
// 获取表单数据
var writeFormData = $writeForm.serializeJSON();
// 发起网络请求
$.ajax({
type: "POST",
url: "/api/v1/blog",
// 在请求结束后执行一些逻辑
success: function (res) {
if (res.success) {
alert("操作成功");
} else {
alert("操作失败");
}
}
});
});
});
</script>
第三步: 后端处理前端提交的 title
, content
等数据, 将数据存入数据表, 并将存储结果返回给前端;
第四步: 前端在收到后端的返回结果后, 执行对应的消息提示;
第五步: END;
======== 本文结束 ========