本文摘自微信公众号 程序员GitHub: 从入门到入土:MySQL完整学习指南,包教包会!
SQL(Structured Query Language),语义是结构化语言,是一门ANSI的标准计算机语言。
数据库(database)是 保存一个文件或一组文件的容器。
数据库管理系统(Database Manage System),用来管理数据库的,比如MySQL、Access、DB2、Informix、Server、Oracle、Sybase等等。
表是一种结构化的文件,可以用来存储特定类型的数据。
每个表的 表名是唯一,不可重复。
列是表中的一个字段,一个表由多个列组成;每个列都由特定的数据类型,只能存放指定数据类型的数据。
数据类型是限定表中的每个列只能存储特定类型的数据,常见的数据类型有整型、数字、文本、字符串、日期 等。
行是表中的一条记录。
主键是每行的唯一标识,特性是主键,不能为空、不能重复、不能修改。
行号指表中每个行的行号。
安装MySQL和使用 Navicat 连接数据库。
新建一张学生表student
,列分别是id
、名称name
、年龄age
、学生信息info
。
建表语句:
CREATE TABLE IF NOT EXISTS `student` (
`id` INT AUTO_INCREMENT,
`name` VARCHAR (100) NOT NULL,
`age` INT NOT NULL,
`info` VARCHAR (40) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ENGINE=InnoDB
设置存储引擎,CHARSET
指定编码格式向表中插入数据:
INSERT INTO `student` (id,name,age,info) VALUES (1,'user1',18,'大一新生');
INSERT INTO `student` (id,name,age,info) VALUES (2,'user2',20,'毕业生');
INSERT INTO `student` (id,name,age,info) VALUES (3,'user3',27,'社会人士');
INSERT INTO `student` (id,name,age,info) VALUES (4,'user4',17,'高三学子');
select
关键字SQL都是由许多关键字(keyword)组成的语句,关键字是数据库的保留字,用户不能将其当做建表的表名、字段等;表中的数据检索使用select关键字作为开头进行查询数据库表的信息。
SELECT name FROM student;
user1
user2
user3
user4
;
隔开;SELECT name,age FROM student;
user1 18
user2 20
user3 27
user4 17
SELECT * FROM student;
1 user1 18 大一新生
2 user2 20 毕业生
3 user3 27 社会人士
4 user4 17 高三学子
*
表示返回表中的所有列,不是必须不建议使用通配符,会影响数据库性能。distinct
去重distinct
表示区分,指检索出来的行是唯一(去重),其放在列的最前面;
如果使用了关键字distinct
,其作用于后面的所有列。
INSERT INTO `student` (id,name,age,info) VALUES (5,'user4',17,'高三学子');
SELECT DISTINCT name,age FROM student;
user1 18
user2 20
user3 27
user4 17
access 和 sql server:
SELECT TOP 2 FROM student;
TOP 2
表示限制返回前2行postgresql、SQLLite、和MySQL:
SELECT name FROM student LIMIT 2;
LIMIT 2
表示限制返回前2行
执行结果:
user1
user2
DB2:
SELECT name FROM student FETCH FIRST 2 ROWS ONLY;
FETCH FIRST 2 ROWS ONLY
表示只抓取前2行数据SELECT name FROM student LIMIT 1 OFFSET 1;
表示查询列名称来自学生表 限制条数1,偏移值1;
意思就是查询学生表中的第二行数据;
offset
表示跳跃或者偏移
执行结果:
user2
MySQL和MariaDB简化形式:
SELECT name FROM student LIMIT 1,2;
表示查询字段名称来自学生表,限制2条,偏移1条;
注意顺序
执行结果:
user2
user3
ORDER BY
子句SELECT name,age FROM student ORDER BY age;
检索字段名称,年龄来自学生表按照列年龄排序;
注意 默认是升序ASC
;
ORDER BY
子句通常在语句末尾
执行结果:
user4 17
user4 17
user1 18
user2 20
user3 27
SELECT name,age FROM student ORDER BY age DESC, name ASC;
查询名称,年龄来自学生表,按年龄降序,名称升序进行排序;
关键字 DESC(descending)
指降序,字母默认Z-A
,
ASC(ascending)
指升序,字母默认A-Z
;
多列情况下,每个列后面指定使用DESC
,使用逗号,
隔开。如果不写,默认升序。
执行结果:
user3 27
user2 20
user1 18
user4 17
user4 17
DESC
和ASC
SELECT name,age FROM student ORDER BY 2 DESC, 1 ASC;
按位指查询字段的位置,2对应字段age,1对应字段name,结果和多列排序一致
执行结果:
user3 27
user2 20
user1 18
user4 17
SQL语句中过滤条件(filter condition)的关键字是WHERE
,跟在表名后面。
WHERE
语句操作符不同数据库管理系统,其支持的操作符略有不同。
操作符 | 说明 |
---|---|
= | 等于 |
> | 大于 |
< | 小于 |
!= | 不等于 |
<> | 不等于 |
>= | 大于等于 |
<= | 小于等于 |
!< | 不小于 |
!> | 不大于 |
BETWEEN | 在中间 |
IS NULL | 为空 |
WHERE
SELECT * FROM student WHERE name = 'user1'
输出结果:
1 user1 18 大一新生
AND
和OR
使用 AND
或 OR
子句:
AND
连接表达式表示过滤条件都为真的数据;OR
连接表达式表示匹配过滤条件任意一个AND
示例:
SELECT * FROM student WHERE age >= 18 AND age <= 23;
条件:学生年龄大于等于18 并且 学生年龄小于 23
执行结果:
1 user1 18 大一新生
2 user2 20 毕业生
OR
示例:
SELECT * FROM student WHERE age >= 18 OR age <= 23;
AND
和OR
示例:
SELECT * FROM student WHERE age >= 18 AND (age <= '23' OR id >= 2);
使用OR
和AND
时应明确过滤条件,用小括号括起来,因为数据库管理系统按顺序执行,不用括号括起来很容易造成语义错误;
过滤条件:查询年龄大于18 并且 (年龄大于等于23或id大于等于2) 的数据
查询结果:
1 user1 18 大一新生
2 user2 20 毕业生
3 user3 27 社会人士
BETWEEN
SELECT * FROM student WHERE age BETWEEN 18 AND 23
查询年龄在18到23之间的(包含18和23)
查询结果:
1 user1 18 大一新生
2 user2 20 毕业生
IS NULL
SELECT * FROM student WHERE age IS NULL
数据库表不填充数据默认为空(NULL
),当然也可给指定类型的列设置默认值
过滤条件:查询年龄为空的数据
查询结果(因为insert
的数据age
都有值,所以返回为空):
空
IN
操作SELECT * FROM student WHERE age IN (18,20,27);
查询条件:年龄在 18 或 20 或 27 的数据
IN
是范围查询,匹配小括号中指定的任意值,功能跟OR
类似,一个IN
相当于好多个OR
查询结果:
1 user1 18 大一新生
2 user2 20 毕业生
3 user3 27 社会人士
NOT
操作符SELECT * FROM student WHERE NOT age = 20;
NOT
操作符表示否定,跟在WHERE
后面,功能类似<>
查询结果:
1 user1 18 大一新生
3 user3 27 社会人士
4 user4 17 高三学子
5 user4 17 高三学子
NOT
和IN
的查询:
SELECT * FROM student WHERE NOT age IN (20, 27);
查询条件:年龄不在 20 或 27 中的数据
执行结果:
1 user1 18 大一新生
4 user4 17 高三学子
5 user4 17 高三学子
通配符:组成匹配模式的特殊字符串。检索文本的通配符用在关键字LIKE
后面。
%
匹配字符前:
SELECT * FROM student WHERE name LIKE '%er2'
查询条件:名称以任意字符开头、以er2
结尾的数据
%
代表任意个任意字符串,包含0
但不包含null
查询结果:
2 user2 20 毕业生
匹配字符后:
SELECT * FROM student WHERE name LIKE '%o%';
s
的数据_
通配符_
匹配一个字符串。在Access数据库中不是_
而是?
SELECT * FROM student WHERE name LIKE '_ser3';
查询条件:匹配名称ser3
前边一个任意字符的数据:
执行结果:
3 user3 27 社会人士
[]
通配符[]
匹配一个位置一个字符,里面可以存放多个字符,关系是or
,模式匹配时只占用一个位置。Access、SQL Server支持
[24]
普通查询:
SELECT * FROM student WHERE name REGEXP '[24]';
查询条件:查询name
包含 2 或 4 的数据
返回结果:
2 user2 20 毕业生
4 user4 17 高三学子
5 user4 17 高三学子
[2-4]
范围查询:
SELECT * FROM student WHERE name REGEXP '[2-4]';
查询条件:查询name
包含 在 2 到 4
的数据,也就是name
中含2或3或4的数据
返回结果:
2 user2 20 毕业生
3 user3 27 社会人士
4 user4 17 高三学子
5 user4 17 高三学子
SELECT CONCAT('你好啊',name,'今天天气怎样?') FROM student WHERE id = 1;
CONCAT
: 合并(拼接)多个数组或多个字符串成一个字符串
不同的数据库管理系统其使用的方式略有差别:
concat
函数||
+
执行结果:
你好啊user1今天天气怎样?
SELECT RTRIM(' 哥,今天管饱 ') FROM student WHERE id=1;
SELECT LTRIM(' 哥,今天管饱 ') FROM student WHERE id=1;
SELECT TRIM(' 哥,今天管饱 ') FROM student WHERE id=1;
RTRIM(str)
函数去掉右边的空字符串;
LTRIM(str)
函数去掉左边的空字符串;
TRIM(str)
函数去掉两边的空字符串。
运行结果:
哥,今天管饱 # 左边有空字符串
哥,今天管饱 # 后边有空字符串
哥,今天管饱 # 两边都没有空字符串
as
# 给字段起别名
SELECT name as student_name FROM student WHERE id=1
# 给表起别名
SELECT name FROM student as s WHERE id=1
# 给字段和表起别名
SELECT name as student_name FROM student as s where id=1
操作符 | 说明 |
---|---|
* | 乘 |
+ | 加 |
- | 减 |
/ | 除 |
SELECT 2 * 8;
先创建三张表并添加数据:
CREATE TABLE IF NOT EXISTS `customer` (
`user_id` INT AUTO_INCREMENT COMMENT '顾客id',
`username` VARCHAR( 255 ) NULL COMMENT '顾客名称',
`telephone` VARCHAR( 255 ) NULL COMMENT '顾客电话',
PRIMARY KEY ( `user_id` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO customer (`user_id`, `username`, `telephone`) VALUES (1, 'zxzxz', '1327');
INSERT INTO customer (`user_id`, `username`, `telephone`) VALUES (2, 'youku1327', '1996');
CREATE TABLE IF NOT EXISTS `product` (
`product_id` INT AUTO_INCREMENT COMMENT '商品id',
`product_name` VARCHAR ( 255 ) NULL COMMENT '商品名称',
`price` VARCHAR ( 255 ) NULL COMMENT '商品价格',
PRIMARY KEY ( `product_id` )
);
INSERT INTO product ( `product_id`, `product_name`, `price` ) VALUES ( 1, '苹果', '5' );
INSERT INTO product ( `product_id`, `product_name`, `price` ) VALUES ( 2, '梨', '4' );
INSERT INTO product ( `product_id`, `product_name`, `price` ) VALUES ( 3, '香蕉', '3' );
CREATE TABLE IF NOT EXISTS `order` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT'订单id',
`user_id` INT NULL COMMENT '客户id',
`product_id` INT NULL COMMENT '商品id',
`order_name` VARCHAR(255) NULL COMMENT '订单名称',
PRIMARY KEY (`id`)
);
INSERT INTO `order` (`id`, `user_id`, `product_id`, `order_name`) VALUES(1, 1, 1, '乖乖订单');
INSERT INTO `order` (`id`, `user_id`, `product_id`, `order_name`) VALUES(2, 2, 2, '悦悦订单');
INSERT INTO `order` (`id`, `user_id`, `product_id`, `order_name`) VALUES(3, 1, 3, '可可订单');
聚集函数的定义:将一些行的数据运行某些函数,返回一个期望值。
avg()
平均值avg()
函数计算行的数量,通过计算这些行的特定列值和,计算出平均值(特定列值之和/行数=平均值)。使用时注意其会忽略列值为null
的行:
SELECT AVG(price) FROM product;
count()
计算行数count()
函数计算行数,count(*)
计算所有行的数目,count("column")
会忽略column
为NULL
的行数:
SELECT count(*) FROM product;
max()
列值的最大值max()
函数返回特定列值的最大值,忽略特定列为NULL
的行:
SELECT MAX(price) FROM product;
min()
列值的最小值min()
函数返回特定列的最小值,忽略特定列为NULL
的行:
SELECT MIN(price) FROM product;
sum()
特定列的和sum()
返回特定列的和,忽略特定列为NULL
的行:
SELECT SUM(price) FROM product;
分组定义:按照特定的列进行分组查询,使用GROUP BY
子句进行分组查询。
注意:
SELECT
后面的列必须出现在group by
子句后面,否则报语法错误;group by
子句的位置是where
条件之后,order by
子句之前。SELECT product_name, SUM(price) FROM product GROUP BY product_name;
SELECT COUNT(*) FROM `order` GROUP BY user_id HAVING COUNT(*) > 1;
语句分析:查询订单表,根据用户id分组,过滤条件条数大于2
注意:having
与 where
其实差别不大:
where
通常当做标准的过滤条件having
用作分组过滤条件having
不支持别名作为分组过滤条件中的一部分SELECT COUNT(*) as count FROM `order` GROUP BY user_id ORDER BY count;
子查询:在查询中嵌套查询。
注意:子查询只能返回单列,若企图返回多列会报语法错误。
SELECT username FROM customer WHERE user_id = ( SELECT user_id FROM `order` WHERE order_name = '乖乖订单' );
联结表,就是关联表查询,主要功能是能在多表中使用一条SQL检索出期望值,但实际库表中是存在的,只在查询期间存在。
分类:
join
关键字SELECT username,order_name FROM customer,`order` WHERE customer.user_id = `order`.user_id;
customer
和order
两张表(username来自customer表,order_name来自order表)中user_id
相等的数据,返回username
和order_name
where
子句后面必须带上两张表的联结关系,否则会出现笛卡尔集(比如3行数据联结另一张表3行数据会产生3*3=9条)内连接(inner join
) 又称等值联结,其查询结果跟之前的简单联结一致。
SELECT username,order_name FROM customer INNER JOIN `order` ON (customer.user_id = `order`.user_id);
自然联结与标准的联结不同就是只返回值唯一的列,不会返回重复的列。
SELECT username,order_name FROM customer INNER JOIN `order` ON (customer.user_id = `order`.user_id);
SELECT * FROM customer INNER JOIN `order` ON (customer.user_id = `order`.user_id);
右外联结是相对于OUTER JOIN
右边的表,查询出右边表的所有数据和根据等值条件匹配左边表的数据,如果左边表的数据不匹配,那么其返回列的值是NULL
充当。
SELECT * FROM `order` RIGHT OUTER JOIN customer ON (customer.user_id = `order`.user_id);
左外联结是相对于OUTER JOIN
左边的表,查询出左边表的所有数据和根据等值条件匹配右边表的数据,如果右边表的数据不匹配,那么其返回列的值是NULL
充当。
SELECT * FROM customer LEFT JOIN `order` ON customer.user_id = `order`.user_id);
左右外联结的区别:其实没什么不同,只是查询表顺序不一致,通过置换表的相对位置可查询出一样的结果。
组合查询:又称为“复合操作”,可以执行多条select
语句,其查询的结构是一致的,返回查询结果。
SELECT user_id FROM customer UNION SELECT user_id FROM `order`;
SELECT user_id FROM customer UNION ALL SELECT user_id FROM `order`;
插入数据库记录是使用insert
关键字,能将一条语句插入数据库,高级的可以组合select
关键字实现插入查询的结果集,插入整张表。
CREATE TABLE IF NOT EXISTS `user` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '用户id',
`name` VARCHAR(255) NULL COMMENT '用户名',
`telephone` VARCHAR(11) NULL COMMENT '手机号',
PRIMARY KEY (`id`)
);
INSERT INTO `user`(id,`name`,telephone) VALUES (2,'zszxz','1327');
语句分析:插入数据到user
表,字段分别是id
,name
,telephone
,值分别是2,‘zszxz’,‘1327’
INTO
可以忽略不写,但不建议,因为在数据库管理系统间会出现移植性问题;
还有字段也可以忽略不写,但不建议,容易造成插入数据出错;
字段的位置和值的位置是一一对应,如果有的位置没值可用NULL
代替
INSERT INTO user
(id,name
) VALUES (3, ‘zszxz’);
将查询的结果插入另一张表,可使用insert select
关键组合成一条语句实现。
INSERT INTO `user` (id,`name`) SELECT id,`name` FROM student WHERE id = 4;
检索一张表的数据全部插入另一张表。有两种方法,但不同的数据库管理系统支持不同,具体如下:
MySQL:
CREATE TABLE student_copy AS SELECT * FROM student;
语句分析:创建表student_copy
数据结构来源查询所有字段来自student
表
错误SQL:
SELECT id,
name
INTO student_copy FROM student;
更新数据库的行使用update
关键字,更新操作是个很危险的操作,在每次执行前都应该检查是否丢了where
子句。
UPDATE student_copy set age = 20;
student_copy
表,设置字段age
值为20,可看到表中所有age
都变成了20;UPDATE student_copy SET age = 18 where id=4;
UPDATE student_copy INNER JOIN student on student.id = student_copy.id SET student_copy.age = student.age, student_copy.`name` = student.`name`;
student_copy
表关联student
表,条件是student
表的id
等于student_copy
表的id
,设置student_copy
表的age
是student
表的age
,name
是student
表的name
。删除表中的行可使用delete
关键字,可删除特定的行或全部,使用时先看是否丢了where
子句。
DELETE FROM student_copy;
student_copy
表DELETE FROM student WHERE id = 4;
student
表条件是id
等于4where
子句select
语句验证sql对数据库的操作分为三种类型,如果学会这三种SQL语言熟练对数据库操作,是登堂入室;如果学会数据库高级操作,说明对数据库有一定的使用经验;如果学会对数据库进行优化,分库分表,读写分离等操作,说明对数据库到了专家级别。
DDL
:数据定义语言(Data Define Language),定义数据的结构。比如:create、drop、alter操作DML
:数据管理语言(Data Manage Language),增删改查。比如:insert、delete、update、select操作DCL
:数据控制语言(Data Control Language),对权限、事务等的控制。比如:grant(授权)、revoke(取回授权)、commit、roolback等。mysql -h 地址 -P 端口 -u 用户名 -p 密码
mysql -h 192.168.0.127 -P 3306 -u root -p root
SELECT DATABASES();
SHOW PROCESSLIST;
SHOW VARIABLES;
SELECT now(), user(), version();
CREATE DATABASE[IF NOT EXISTS] 数据库名 [数据库选项]
create database demo;
DROP DATABASE [IF EXISTS] 数据库名;
drop database demo;
CREATE [TEMPORARY] TABLE [IF NOT EXISTS] [库名.]表名(表的结构定义) [表选项]
其中 TEMPORARY
表示临时表,中括号内容都表示可选,在正规的数据库版本管理开发会经常使用到。
字段的修饰如下数据类型:
[NOT NULL | NULL]
[DEFAULT default_value]
[AUTO_INCREMENT]
[UNIQUE[KEY] | [PRIMARY KEY]]
[COMMENT 'string']
表选项一般就是制定数据库引擎和字符集:
ENGINE=InnnoDB DEFAULT CHARSET=utf8 COMMENT=‘顾客表’
示例:
CREATE TABLE IF NOT EXISTS `customer` (
`id` INT AUTO_INCREMENT COMMENT '主键',
`customer_name` VARCHAR( 255 ) NULL COMMENT '顾客名称',
`gender` varchar(255) NULL COMMENT '性别',
`telephone` VARCHAR( 255 ) NULL COMMENT '电话号码',
`register_time` timestamp NULL DEFAULT NULL COMMENT '注册时间',
PRIMARY KEY ( `user_id` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='顾客表';
SHOW TABLES
SHOW TABLES FROM 数据库名
SHOW TABLES FROM demo_database;
DROP TABLE [IF EXISTS] 表名;
drop table op;
TRUNCATE [TABLE] 表名
CREATE TABLE 表名 LIKE 要复制的表名;
create table op like `order`;
CREATE TABLE 表名 [AS] SELECT * FROM 要复制的表名;
CREATE TABLE op AS SELECT * FROM `order`;
alter
操作alter table [数据库名.]表名 add [column] 字段 数据类型;
alter table `order` add column `year` year;
alter table [数据库名.]表名 add [column] 字段 数据类型 first;
alter table [数据库名.]表名 add [column] 字段 数据类型 after 另一个字段;
alter table [数据库名.]表名 modify [column] 字段名 新的数据类型;
alter table `order` modify column `gender` tinyint;
alter table [数据库名.]表名 modify [column] 字段名 数据类型 first;
alter table [数据库名.]表名 modify [column] 字段名 数据类型 after 另一个字段名;
alter table [数据库名.]表名 change [column] 旧字段名 新字段名 数据类型;
alter table [数据库名.]表名 ADD PRIMARY KEY (
字段名
);
alter table `order` add primary key (`id`);
alter table [数据库名.]表名 ADD UNIQUE [索引名] (字段名)
alter table [数据库名.]表名 ADD INDEX [索引名] (字段名)
alter table [数据库名.]表名 drop [column] 字段名
alter table `order` drop column `gender`;
alter table [数据库名.]表名 DROP INDEX 索引名
alter table [数据库名.]表名 DROP PRIMARY KEY
alter table [数据库名.]表名 DROP FOREIGN KEY 外键
视图是一张虚表,本质上SQL的检索语句,不存储任何的数据成分。
视图好处:
说明:
视图需要MySQL5.0以上才支持
create view
语句用于创建视图
show create view viewName
drop view viewName
create or replace view
create view `view_order` as SELECT `id`,`order_name`,`year` FROM `order`;
order
表的id
、order_name
、year
三个字段组成视图,as
后面就是查询语句,也可以是子查询、多表关联等复杂的查询语句SELECT * FROM `view_order`;
order
中INSERT INTO `view_order` (`order_name`,`year`) VALUES ('小可可的订单',2021);
drop view `view_order`;
概念:使用多条语句完成业务的操作。简单的定义存储过程就是多条SQL的集合。
特点:
Create PROCEDURE 存储过程名称 (参数列表)
begin
过程体
end;
IN
输入IN var1 Declmal(6,2)
OUT
输出IN var2 Decimal(6,2)
INOUT
输入输出IN var3 Decimal(6,2)
declare 变量名称 变量类型 [default value]
call 存储过程名称
DROP PROCEDURE 存储过程名称
使用 set
和 select into
语句为变量赋值
set @var := 20
select sum(price) into total from table_name
if
语句f 条件 then
表达式
[elseif 条件 then
表达式]
...
[else
表达式]
end if;
case
语句CASE 值 WHTN 匹配值 THEN 结果
[WHEN 匹配值 THEN 结果]
......
[ELSE 结果]
END
while
语句[开始标签:]while 条件 do
循环体
[结尾标签]
end while;
loop
语句[开始标签:] loop
语句体
[结尾标签]
end loop;
iterate/leave
语句通过标签可以实现:
iterate
表示迭代leave
表示离开repeat
语句repeat
--循环体
until 循环条件
end repeat;
知识点:如果用命令行学习,在写多行SQL时 使用 //
可实现换行。
准备张表order_detail
并插入几条数据
CREATE TABLE `order_detail` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`detail_name` varchar(255) DEFAULT NULL COMMENT '订单明细',
`price` decimal(10,2) DEFAULT NULL COMMENT '价格',
`oid` int(11) DEFAULT NULL COMMENT '订单id',
PRIMARY KEY (`id`)
)
CREATE PROCEDURE slelect_detail ( ) BEGIN
SELECT
detail_name
FROM
order_detail;
END;
CALL slelect_detail ( );
DROP PROCEDURE slelect_detail;
oid
为动态的所有订单明细名称,考虑到oid
为动态,需要用户输入,故将oid
作为入参:CREATE PROCEDURE slelect_detail ( IN order_id INT ) BEGIN
SELECT
detail_name
FROM
order_detail
WHERE
oid = order_id;
END;
oid
为1的用户的订单明细名称call slelect_detail(1);
DROP PROCEDURE slelect_detail;
id
为order_id
,输出总金额为total
CREATE PROCEDURE slelect_toatal_money ( IN order_id INT, OUT total DECIMAL ( 8, 2 ) )
BEGIN
SELECT
sum( price ) INTO total
FROM
order_detail
WHERE
oid = order_id;
END;
CALL slelect_toatal_money ( 1, @total );
order_id
为1总金额示例SELECT @total;
drop PROCEDURE slelect_toatal_money;
if
语句示例使用控制流程,实现复杂的存储过程。
对输入的order_id
自动加5
,然后判断var
是否小于7
,如果是就查询订单明细价格,否则查询订单明细价格总和:
create procedure slelect_toatal_money(IN order_id INT)
begin
-- 定义变量
declare var int;
-- 赋值
set var= order_id+5;
-- if 判断
if var<7 then
select price from oder_detail where oid = order_id;
else
select sum(price) from oder_detail where oid = order_id;
end if;
end;
CALL slelect_toatal_money(1);
CALL slelect_toatal_money(2);
DROP PROCEDURE slelect_toatal_money;
while
语句示例var
进行判断,如果var
<7就指向查询价格语句,并且var
进行自增CREATE PROCEDURE slelect_toatal_money(IN order_id INT)
BEGIN
-- 定义变量
DECLARE var INT;
-- 赋值
SET var = order_id + 5;
-- while
while var < 7 DO
SELECT price FROM order_detail WHERE oid = order_id;
SET var = var + 1;
END WHILE;
END;
CALL slelect_toatal_money ( 1 );
case
语句示例if
语句实现效果一致:CREATE PROCEDURE slelect_toatal_money(IN order_id INT)
BEGIN
-- 定义变量
DECLARE var INT;
-- 赋值
SET var := order_id;
-- case 判匹配
CASE var
WHEN 1 THEN
SELECT price FROM order_detail WHERE oid = order_id;
WHEN 2 THEN
SELECT SUM(price) FROM order_detail WHERE oid = order_id;
END CASE;
END;
call slelect_toatal_money(1);
CALL slelect_toatal_money(2);
loop
语句var
小于3就计算 价格+var
的值CREATE PROCEDURE slelect_toatal_money(IN order_id INT)
BEGIN
-- 定义变量
DECLARE var INT;
-- 赋值
SET var := order_id;
-- loop
select_loop : LOOP
SELECT price + var FROM order_detail WHERE oid = order_id;
SET var = var + 1;
-- 跳出循环
IF var > 3 THEN
LEAVE select_loop;
END IF;
END loop;
END;
CALL slelect_toatal_money(1);
CALL slelect_toatal_money(2);
repeat
repest
与while
不同之处:while
在执行之前检查条件;repest
在执行之后检查条件:CREATE PROCEDURE slelect_toatal_money(IN order_id INt)
BEGIN
-- 定义变量
DECLARE var INT;
-- 赋值
SET var = order_id + 5;
-- repeat循环
REPEAT
SELECT price FROM order_detail WHERE oid = order_id;
SET var = var + 1;
UNTIL var > 7
END REPEAT;
END;
CALL slelect_toatal_money(1);
知识点:
loop
、while
、repeat
、iterate
都是循环loop
、while
、repeat
功能几乎相同iterate
可通过标签的形式调用循环,与leave
语句使用方式一样游标本质:查询后的结果集。对查询的结果集进行前一行或后一行类似的操作时就可以使用到游标
create procedure 存储过程名称()
begin
– 游标 –
– xx名称,打开游标后抓取每行,将结果赋值给name
declare name varchar(20);
– 创建游标
declare 游标名称 cursor for 查询语句;
– 打开游标
open 游标名称;
– 对查询的结果集(即游标)进行检索行至变量提供使用
fetch 游标名称 into name;
select name;
– 关闭游标
close 游标名称;
end;
– 调用存储过程
call 存储过程名称;
–删除存储过程
drop procedure 存储过程名称;
需求:查询oid
为1的订单明细名称的结果集作为游标
name
CREATE PROCEDURE printName()
BEGIN
-- 订单名称
DECLARE name VARCHAR(20);
-- 创建游标
DECLARE cur CURSOR FOR SELECT detail_name FROM order_detail WHERE oid = '1';
-- 打开游标
OPEN cur;
FETCH cur INTO name;
SELECT name;
-- 关闭游标
CLOSE cur;
END;
CALL printName;
name
和detail_price
,在循环无法继续时会出现SQLSTATE '02000'
,即此通过变量continue
时设置done
为1代表true
,此时循环结束,跳出循环:DROP PROCEDURE IF EXISTS printDetail;
CREATE PROCEDURE printDetail()
BEGIN
-- 订单名称
DECLARE name varchar(20);
-- 价格
DECLARE detail_price DECIMAL(8,2);
-- 结束标志变量(默认为假)
DECLARE done boolean DEFAULT 0;
-- 创建游标
DECLARE cur CURSOR FOR SELECT detail_name, price FROM order_detail WHERE oid='1';
-- 指定游标循环结束时的返回值
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
-- 打开游标
OPEN cur;
-- 循环游标数据
detail_loop:LOOP
-- 根据游标当前指向的一条数据
FETCH cur INTO name, detail_price;
SELECT name, detail_price;
-- 判断游标的循环是否结束
IF done THEN
-- 跳出游标循环
LEAVE detail_loop;
END IF;
END LOOP;
-- 关闭游标
CLOSE cur;
END;
CALL printDetail();
触发器:当表发生改变的时候触发的动作。
例子:当往表中插入数据时,此时表发生了改变,现在想要在每次插入数据前检测所有的入参是否都是小写。此时就可以用触发器检测。
经上面分析得知,使用一个基本的触发器,至少表要发生改变,还要满足一个被触发的事件。
表发生改变通常指 增删改,其动作可以发生在增删改之前或之后,触发事件就是我们需要写的过程:
知识点:
insert
触发器示例创建一个触发器getPrice
作用于order_detail
表的每行,每当插入数据之后就查询这条订单明细的价格赋值给变量 @price
。
NEW
是一张虚表,记录着被插入数据的行,因此能在NEW
表中获取每次插入的数据。
-- insert 触发器
CREATE TRIGGER getPrice AFTER INSERT ON order_detail FOR EACH ROW SELECT NEW.price INTO @price;
-- 检测插入触发器
INSERT INTO `order_detail`(`detail_name`,`price`,`oid`) VALUES ('脸盆',20.00,2);
SELECT @price;
DROP TRIGGER getPrice;
update
触发器示例将插入后触发器改为更新后的触发器,只需改动after insert
为after update
即可。
UPDATE `order_detail` SET `price` = 30.00 WHERE `id` = 2;
SELECT @price;
-- 删除触发器
DROP TRIGGER getPrice;
将更新触发器的NEW
表改为OLD
表
CREATE TRIGGER getPrice AFTER UPDATE ON order_detail FOR EACH ROW SELECT OLD.price INTO @price;
更新价格为40
UPDATE `order_detail` SET `price` = 40.00 WHERE `id` = 2;
此时查询价格为30,说明OLD表触发的是原始数据值
SELECT @price;
知识点:
delete
触发器将 更新触发器 改为 delete触发器。之前省略了begin、end
,如果是多条执行语句则需要加上:
CREATE TRIGGER getPrice AFTER DELETE ON order_detail FOR EACH ROW
BEGIN
SELECT OLD.price INTO @price;
END;
删除之前的SQL数据
DELETE FROM order_detail WHERE `id` = 2;
id
=2的数据被删除查询价格为40,OLD
表存放的是将要被删除的数据:
SELECT @price;
有关用户账号的信息储存MySQL的mysql
数据库,故如果需要查看用户信息,则需要进入mysql
数据库。
user
表存储了所有的登录账号,使用MySQL库查询user
表中的user
:
USE mysql;
SELECT `user` FROM user;
CREATE USER 用户名 IDENTIFIED BY [PASSWORD] 密码;
示例:创建用户 Jason
,并指定密码为 Python
:
CREATE USER Jason IDENTIFIED BY 'Python';
然后
SELECT `user` FROM user;
查询结果
RENAME USER 旧用户名 TO 新用户名;
示例:重命名用户Jason
为Silence
:
RENAME USER Jason TO Silence;
然后
SELECT `user` FROM user;
查询结果
DROP USER 用户名;
示例:删除用户Silence
:
DROP USER Silence;
然后查询结果则没有Silence
用户了。
SET PASSWORD FRO 用户名 = PASSWORD(‘密码’)
示例:为用户Jason
更改密码为py
SET PASSWORD FOR Jason = PASSWORD('py');
SHOW GRANTS FOR 用户名;
示例:查看用户Jason
拥有的权限
SHOW GRANTS FOR Jason;
GRANT 权限 ON 表名 TO 用户名 [IDENTIFIED BY [PASSWORD] ‘password’]
常见的权限:all
、create
、drop
、insert
、update
、select
。
示例:给用户Jason
分配test
库中所有表的查询权限
REVOKE 权限列表 ON 表名 FROM 用户名;
示例:撤销用户Jason对test库里所有表的查询操作
REVOKE SELECT ON test.* FROM Jason;
使用授权,撤销权限时可参考如下权限列表:
权限 | 说明 |
---|---|
ALL | 除 GRANT OPTION 外的所有权限 |
ALTER | 使用ALTER TABLE |
ALTER ROUTINE | 使用 ALTER PROCEDURE 和 DROP PROCEDURE |
CREATE | 使用 CREATE TABLE |
CREATE ROUTINE | 使用 CREATE PROCEDURE |
CREATE TEMPORARY TABLES | 使用CREATE TEMPORARY TABLE |
CREATE USER | 使用CREATE USER、DROP USER、RENAME USER和REVOKE ALL PRIVILEGES |
CREATE VIEW | 使用CREATE VIEW |
DELETE | 使用DELETE |
DROP | 使用DROP TABLE |
EXECUTE | 使用CALL和存储过程 |
FILE | 使用SELECT INTO OUTFILE和LOAD DATA INFILE |
GRANT OPTION | 使用GRANT和REVOKE |
INDEX | 使用CREATE INDEX和DROP INDEX |
INSERT | 使用INSERT |
LOCK TABLES | 使用LOCK TABLES |
PROCESS | 使用SHOW FULL PROCESSLIST |
RELOAD | 使用FLUSH |
REPLICATION CLIENT | 服务器位置的访问 |
REPLICATION SLAVE | 由复制从属使用 |
SELECT | 使用SELECT |
SHOW DATABASES | 使用SHOW DATABASES |
SHOW VIEW | 使用SHOW CREATE VIEW |
SHUTDOWN | 使用mysqladmin shutdown(用来关闭MySQL) |
SUPER | 使用CHANGE MASTER、KILL、LOGS、PURGE、MASTER和SET GLOBAL。还允许mysqladmin调试登录 |
UPDATE | 使用UPDATE |
USAGE | 无访问权限 |
MySQL的层级大概可以分为3类:
MySQL的Query Cache
是基于hash值计算进行匹配的缓存机制。在大数据量的情况下如果开启Query Cache
会频繁的计算Hash,会增加性能的消耗,得不偿失,在生产环境建议关闭该选项。
可使用语句:show VARIABLES like '%query_cache%'
查看Query Cache
是否关闭。主要关注参数query_cache_type
是否关闭(OFF关闭,ON开启),不用过于关注缓存分配大小的query_cache_size
参数。
MySQL中根据不同的引擎,主要出现三类锁的情况:表锁、读锁、写锁。
读锁:也是共享锁,即多用户状态下同一时间对资源的读取互不影响,但不能对数据进行修改等操作。
使用场景:一般情况下手动给一条或某个范围内(一般是用在存储过程)的数据加上读锁。
读锁语法示例:
select 字段 from 表名 [where 条件] lock in share mode;
写锁:是"排他锁",也称"独立锁"。
使用场景:一般是写入数据的情况,一个用户如果获得写锁,其他用户将不能获取写锁或读锁,直到该用户执行完操作并释放锁。
使用方式:在执行语句后加for update
语句
写锁语法示例:
select 字段 from 表名 [where 条件] for update;
锁粒度:对资源锁定范围的一个程度,使用不同的锁定策略达到并发性能较优的结果。
锁粒度使用策略情况分为:行锁、表锁、页锁。
概念:对整张表进行加锁。
优缺点:
手动加表锁语法示例:
lock table 表名
释放锁:
unlock tables 表名
概念:对行进行锁定。
优缺点:
行锁的种类:
通常情况下遇不到页锁,其开销和加锁时间介于表锁和行锁之间,会出现死锁,锁定粒度介于表锁和行锁之间。
知识点:
乐观锁基于版本号实现。
注意点:条件必须是主键,读取时将数据版本号读出,更新数据时,版本号加1,将查询的数据进行对比,如果版本号不一致就是过期数据。
查询示例:
select id,value,version from 表名 where id=#{id}
更新示例:
update 表名 set value=2,version=version+1 where id=#{id} and version=#{version}
表锁、行锁、读写锁都是悲观锁。
MySQL支持多种引擎,主流使用的引擎是InnoDB,其次是MyISAM,特殊情况下使用Memory。
使用最广泛的引擎,也是最重要的引擎。
存储性能:
next key lock
,防止 的幻读 出现MyISAM在早期版本是MySQL的默认引擎,在MySQL5.1之后不再使用。
特点:不支持事务,不支持行锁,默认表锁,并发量低
存储内容都是存放在引擎当中。
特点:
锁等待,就是session(事务会话,开启一个事务代表一个会话) A 对某行数据获取独占锁(一般就是写锁),然后session B 对相同的行进行获取独占锁就发生了锁等待。MySQL有一个保留参数innodb_lock_wait_timeout
指定死锁的时间,如果超过死锁等待时间就是报异常。
示例:
begin
update `order` set `year`='2021' where id='1';
begin
update `order` set `year`='2021' where id='1';
lock wait timeout exceeded; try restarting transaction
show BARIABLES like 'innodb_lock_wait_timeout'
两个以上的会话在抢占资源过程中,产生互相等待的情况。
死锁建立在锁等待的基础上,session A 获取id=1的写锁,session B 获取id=2的写锁,此时由于索引不同,顾不会发生锁等待现象;当session A 尝试获取id=2的写锁时,由于id=2已经被session A获取,此时产生锁等待,由于 sessionA 和 session B 同时都在锁等待状态,产生了等待对方释放锁,故会产生死锁。
示例:
BEGIN;
UPDATE `order` SET `year`='2021' WHERE id=1;
BEGIN;
UPDATE `order` SET `year`='2022' WHERE id=2;
UPDATE `order` SET `year`='2022' WHERE id=2;
update `order` set `year`= '2022' where id = '1';
当 B 进入 锁等待后就直接报死锁异常
Deadlock found when trying to get lock; try restarting transaction
可使用 show engine innodb status
查看死锁
......
*** (1) TRANSACTION: // 事物A
TRANSACTION 253507, ACTIVE 474 sec starting index read
mysql tables in use 1, locked 1 // 已经使用一个锁
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 17001, OS thread handle 139824777217792, query id 2191731 ......
root updating
update `order` set `year`= '2022' where id = '2'//执行得语句
*** (1) WAITING FOR THIS LOCK TO BE GRANTED: // 等待锁释放获取锁
RECORD LOCKS space id 65 page no 3 n bits 80 index PRIMARY of table `zszxz`.`order` trx id 253507 lock_mode X locks rec but not gap waiting
.....
*** (2) TRANSACTION: // 事物 B
TRANSACTION 253508, ACTIVE 425 sec starting index read
mysql tables in use 1, locked 1 // 已经使用一个锁
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 17002, OS thread handle 139824778569472, query id 2191735 ......
root updating
update `order` set `year`= '2022' where id = '1'//执行得语句
*** (2) HOLDS THE LOCK(S): //持有锁
RECORD LOCKS space id 65 page no 3 n bits 80 index PRIMARY of table `zszxz`.`order` trx id 253508 lock_mode X locks rec but not gap
......
*** (2) WAITING FOR THIS LOCK TO BE GRANTED: // 等待锁释放获取锁
RECORD LOCKS space id 65 page no 3 n bits 80 index PRIMARY of table `zszxz`.`order` trx id 253508 lock_mode X locks rec but not gap waiting
......
字母代表锁的类型如下:
可以看出上面的语句(1)代表 事务A,MySQL线程ID17001,(2)代表 事务B,MySQL线程ID17002,事务A与B都在等待对方释放锁,产生了死锁。
知识点:
show status like 'table%';
解决死锁: