SQL语法
DDL
DDL 数据库操作
1SHOW DATABASES; -- 查询所有数据库
2SELECT DATABASE(); -- 查询当前数据库
3CREATE DATABASE IF NOT EXISTS learndatabase DEFAULT CHARSET utf8mb4; -- 创建数据库
4USE learndatabase; -- 使用数据库
5DROP DATABASE IF EXISTS learndatabase; -- 删除数据库
DDL 表结构
1SHOW TABLES; -- 查询数据库所有的表
2DESC tableName; -- 查询表结构
3SHOW CREATE TABLE tableName; -- 查询表的建表语句
4
5CREATE TABLE tableName( -- 创建表
6 id INT COMMENT '编号',
7 name VARCHAR(50) COMMENT '姓名',
8 age INT COMMENT '年龄',
9 gender VARCHAR(1) COMMENT '性别'
10) COMMENT '用户表';
11
12ALTER TABLE tableName ADD nickname VARCHAR(20) COMMENT '昵称'; -- 修改表,添加字段
13alter table tb add date DATE comment '入职日期';
14ALTER TABLE tableName CHANGE nickname username VARCHAR(30) COMMENT '用户名'; -- 修改字段名,同时修改字段类型
15ALTER TABLE tableName MODIFY username CHAR(30); -- 修改数据类型
16ALTER TABLE tableName DROP username; -- 删除字段username
17
18ALTER TABLE tableName RENAME TO tb; -- 修改表名
19
20DROP TABLE IF EXISTS tableName; -- 删除表
21TRUNCATE TABLE tableName; -- 删除表,重新创建该表;其实就是清空数据
数据类型
三大类:数值,字符串,日期.
- TINYINT 1byte 取值在-128,127,无符号在0,255. 于是age字段就可以这样: age TINYINT UNSIGNED
- DOUBLE(4,1) 最长4为,允许出现1位小数 于是成绩字段score DOUBLE(4,1)
- CHAR 定长字符串,性能高,没有的部分用空格填补,适合用变化不大的字段,如gender CHAR(1)
- VARCHAR 变长字符串,对于字符串长度变化很大的字段比较适合,如 username VARCHAR(50)
- DATE是年月日
- TIME是时分秒
- YEAR就是年;
- DATETIME是年月日时分秒, birthday DATE
- TIMESTAMP也是年月日时分秒,但是最大范围是2038年
DML
DML 数据操作语言INSERT UPDATE DELETE
DML INSERT
添加字符串,日期要加引号; 数据范围要符合要求
1-- 选择字段插入
2INSERT INTO tb(id, name, age, gender, username, date) VALUES (1, '赵四', 13, '男', '四娘', '2001-05-28');
3-- 全部字段插入
4insert into tb values (1, '张三', 15, '女', '三爹', '2005-05-05');
5-- 插入多条数据
6insert into tb values (3, '王五', 29, '女', '五爹', '1998-12-15'),(4, '李四', 30, '男', '四爹', '1970-01-30');
DML UPDATE
1-- 修改
2update tb set date='2010-09-01' where name='赵四';
3update tb set id=2, date='2001-05-28' where username='三爹';
4-- 没有加where, 改所有数据
5update tb set date='2000-01-01'
DML DELETE
1-- 删除
2delete from tb where id=1 and name='赵四';
3-- 删除索引
4delete from tb;
DQL
DQL 数据查询语言
- select 字段列表
- from 表名列表
- where 条件列表
- group by 分组字段列表
- having 分组后的条件列表
- order by 排序字段列表
- limit 分页参数
1-- 查询多个字段
2select id, name from tb;
3-- 查询所有, 写*效率不高,最好写出所有字段,清晰明了
4select * from tb;
5-- 设置别名
6select id as '爱迪', name as '姓名' from tb;
7-- 查询去重
8select distinct * from tb;
条件查询
- >=, <, <= , = , !=(<>), between and, in, like(_单个字符,%任意字符), is null
1-- and or not, && || !
2select * from tb where age = 13;
3select * from tb where age < 20;
4select * from tb where age <= 20;
5select * from tb where username is null ; -- 查询没有username字段的数据
6select * from tb where username is not null ; -- 查询有username字段的数据
7select * from tb where age != 13;
8select * from tb where age <> 13;
9select * from tb where age <= 20 && age >= 10;
10select * from tb where age <= 20 and age >= 10;
11select * from tb where age between 10 and 20; -- between and 包括最大值和最小值
12select * from tb where age=10 or age=13 or age=20;
13select * from tb where age in(10,13,20); -- age在in里面的数其一即可
14select * from tb where name like '__'; -- 查询姓名只有两个字符的数据
15select * from tb where username like '%x'; -- 查询username最后一位是x的数据
聚合函数
聚合函数,将一列数据作为整体, 纵向计算
count-数量, max min, avg-平均值, sum-求和
1-- 聚合函数不计算null值
2select sum(age) from tb;
3select count(1) from tb;
4select count(*) from tb;
5select avg(age) from tb;
6select max(age) from tb;
7select sum(age) from tb where id=1;
分组查询
分组查询 group by, 分组之后一般查询分组字段和聚合函数, 其他的无意义
1select gender, count(*) from tb group by gender; -- 根据性别分组,分组后统计数量,相当于对每个组分别处理
2select gender, avg(age) from tb group by gender; -- 性别分组,统计男女的平均年龄
3
4-- where在分组前进行过滤, having在分组后过滤,having可以对聚合函数过滤,where不行.
5-- 虽然组合在一起的sql语句很麻烦,但是一步一步地加条件来看,就不麻烦了,sql就是在简单查询结果基础上一步一步处理的.
6-- 先where得到有条件的表,然后group by得到两行分组后的数据,最后对这两行数据进行having筛选.
7select gender, count(*) from tb where age<= 30 and age >= 10 group by gender having count(*) > 2;
8-- 给聚合起别名
9select gender, count(*) as gender_count from tb where age<= 30 and age >= 10 group by gender having gender_count > 2;
排序查询
排序查询 order by
order by多字段,当第一个字段相同则按照第二个字段排序, asc升序 desc降序
1select * from tb order by age asc;
2select * from tb order by age desc;
3select * from tb order by date desc;
4-- 先按照age升序,age相同再按照date降序排序
5select * from tb order by age asc ,date desc;
分页查询
1-- 分页查询 limit 起始索引,查询记录数
2-- 起始索引从0开始, 起始索引=(查询页码-1)*每页记录数
3-- 如果是第一页数据,可以省略
4select * from tb limit 0, 2;
5select * from tb limit 2, 3;
DQL执行顺序:
编写顺序: select from where group by having order by limit
执行顺序: from where group by having select order by limit
DCL
DCL 数据控制语言 管理用户,控制访问
DCL 管理用户
1-- 查询用户
2use mysql;
3select * from user;
4-- 创建用户 用户名@主机名 后面是密码,创建的用户没有权限
5-- 创建用户bnaod,只能在当前主机访问,密码password
6create user 'bnaod'@'localhost' identified by 'password';
7-- 创建用户,在任意主机访问
8create user 'bnaod'@'%' identified by 'password';
9-- 修改用户密码
10alter user 'bnaod'@'localhost' identified with mysql_native_password by 'newpassword';
11-- 删除用户
12drop user 'bnaod'@'localhost';
DCL 权限控制
1-- 查询权限
2show grants for 'bnaod'@'localhost';
3-- 授予权限 on后接数据库名.表名 *代替全部 如*.*表示所有数据库的所有表 后面是用户
4grant all on learndatabase.* to 'bnaod'@'localhost';
5-- 撤销权限
6revoke all on learndatabase.* from 'bnaod'@'localhost';
函数
字符串函数
- concat字符串拼接函数, lower转小写, upper转大写\
- lpad(str,n,pad) 左填充,用字符串pad对str左边填充,达到n长度
- rpad(str,n,pad) 右填充,用字符串pad对str右边填充,达到n长度
- trim 去掉字符串头部和尾部的空格
- substring(str,start,len)返回字符串str从start位置起的len个长度的字符串
1select concat('hello', ' mysql');
2select lower('HEllo');
3select upper('HEllo');
4select lpad('01', 5, '@');
5select rpad('01', 5, '@');
6select trim(' HELLO MYSQL ');
7select substring('HELLO,MYSQL', 1, 5); -- 这个不是字符串下标,就是单纯的第一个
8-- 将编号补成5位数,如1->00001
9select lpad(id, 5, '0') from tb;
数值函数
- ceil(x) 向上取整
- floor(x) 向下取整
- mod(x,y) 返回x/y的模
- rand() 返回0~1的随机数
- round(x,y) 对x四舍五入,保留y位小数
1select ceil(1.1);
2select floor(1.1);
3select mod(3,4);
4select mod(4,4);
5select rand();
6select round(pi(), 7);
7-- 生成6位随机验证码
8select lpad(1000000 * round(rand(),6), 6, '0');
日期函数
- curdate() 当前日期
- curtime() 当前时间
- now() 当前日期+时间
- year(date) 取得date的年
- month(date) 取得date的月
- day(date) 取得date的日
- date_add(date, INTERVAL expr type) 返回日期/时间加上一个时间间隔expr后的时间值
- datediff(date1, date2) 返回结束date1和起始date2之间的天数(day)
1select curdate();
2select curtime();
3select now();
4select year(now());
5select year(date) from tb;
6select month(date) from tb;
7select day(date) from tb;
8select date_add(now(), INTERVAL 70 DAY );
9select date_add(now(), INTERVAL 70 MONTH );
10select date_add(now(), INTERVAL 70 YEAR );
11select datediff(now(), '2001-05-28');
12select datediff(now(), '1992-08-17');
13-- 员工入职天数降序排列
14select datediff(now(), date) as jointime from tb order by jointime desc ;
流程控制函数
- if(value, t, f)
- ifnull(value1, value2) 如果value1不为空返回value1,否则返回value2
- case when [val1] then [res1] else [default] end 如果val1为true,返回res1,…,否则返回default 这个相对于if elseif结构
- case [expr] when [val1] then [res1] else [default] end 如果expr等于val1返回res1,….,否则返回default 这个相对于switch结构
1select if(true, 'ok', 'error');
2select if(false, 'ok', 'error');
3
4select ifnull('ok', 'default');
5select ifnull('', 'default');
6select ifnull(null, 'default');
7
8select case when true then 'res1' else 'default' end;
9
10-- 查询员工年龄,如果大于50就显示老毕登, 如果大于15就显示中壁灯, 如果小于15就显示小壁灯
11select id,
12 name,
13 (case when age>=50 then '老毕登' when age>=15 then'中壁灯' else '小壁灯' end) as nickname
14from tb ;
约束
这些东西idea可以直接图像界面改动.
- 非空约束 不能为null NOT NULL
- 唯一约束 数据是唯一的 UNIQUE
- 主键约束 一行数据唯一标识,要求非空且唯一 PRIMARY KEY
- 默认约束 未指定该字段的值,则采用默认值 DEFAULT
- 检查约束 保证字段满足一个条件 CHECK
- 外键约束 用来建立两个表的连接 FOREIGN KEY
1create table user(
2 id int primary key auto_increment, -- 主键,且自动增长
3 name varchar(10) not null unique , -- 不为null,唯一
4 age int check ( age > 0 and age <= 120 ), -- 年龄在0到120之间
5 status char(1) default '1', -- 默认为1
6 gender char(1)
7) comment 'user table'
8
9desc user;
10
11insert into user(name, age, status, gender) values ('tom1', 19, '1', '男'), ('tom2', 31, '0', '男');
12insert into user(name, age, status, gender) values (null, 20,'1','女');
13insert into user(name, age, status, gender) values ('tom1', 20,'1','女');
14insert into user(name, age, status, gender) values ('tom3', -1,'1','女');
15insert into user(name, age, gender) values ('tom3', 23,'女');
外键
有外键的是子表,关联的是父表, 建立外键保证数据的一致性和完整性.
1-- 准备
2create table dept(
3 id int auto_increment primary key ,
4 name varchar(50) not null
5);
6insert into dept(id, name) VALUES (1,'研发部'),(2,'市场部'),(3,'财务部'),(4,'销售部'),(5,'总经办');
7alter table user add dept_id int;
8alter table user add manager_id int;
9insert into user(id, name, age, status, gender, dept_id) values (null, 'jerry2', 73, '0', '女', null);
10
11-- 添加外键.在创建表时额外添加一行, 也可以额外修改
12-- constraint [外键名称] foreign key (外键字段名) references 主表(主表列名)
13alter table user add constraint fk_user_dept_id foreign key (dept_id) references dept(id);
14-- 这样父表的字段不能直接删除,因为有子表外键关联
15-- 删除外键
16-- alter table 表名 drop foreign key 外键名称;
17alter table user drop foreign key fk_user_dept_id;
删除更新行为
- no action/restrict 父表中有更新和删除行为,检测有对应外键,有就不允许删除/更新
- cascade 父表删除/更新,有外键,那么也会删除/更新在子表的记录
- set null 父表删除,有外键的话,将子表的值设置为null(要求该外键可以为null)
- set default 父表数据变成,子表将外键设置默认值(innodb不支持)
1-- constraint [外键名称] foreign key (外键字段名) references 主表(主表列名) on update cascade on delete cascade
2-- on update在更新时要进行的行为,on delete在删除时要进行的行为
3alter table user add constraint fk_user_dept_id foreign key (dept_id) references dept(id) on update cascade on delete cascade;
4alter table user add constraint fk_user_dept_id foreign key (dept_id) references dept(id) on update set null on delete set null ;
多表查询
一对多,就是一个外键
多对多,需要维护一个中间表,两个外键
一对一,用作单表拆分,维护一个外键,而且该外键字段需要设置unique,保证它是一对一.
1-- 进行笛卡尔积, 组合的所有情况
2select * from user,dept;
3-- 消除笛卡尔积, 加一个条件, 这里没有外键也可以
4select * from user,dept where dept_id = dept.id;
连接查询
-
内连接:查询AB交集
1-- 隐式内连接 2-- select 字段列表 from 表1,表2 where 条件; 3-- 显示内连接 4-- select 字段列表 from 表1 inner join 表2 on 连接条件; 5-- 查询员工姓名,关联的部门 6select user.name, dept.name from user, dept where user.dept_id=dept.id; -- 隐式内连接 7select u.name, d.name from user u, dept d where u.dept_id=d.id; -- 取别名 8select u.name, d.name from user u inner join dept d on u.dept_id=d.id; -- 显式内连接
-
左外连接:查询左表的所有数据,包括AB交集
-
右外连接:查询右表的所有数据,包括AB交集
1-- 左外连接 完全包含左表(即便没有外键值),还有交集 2-- select 字段列表 from 表1 left [outer] join 表2 on 条件; 3-- 右外连接 完全包含右表(即便没有外键值),还有交集 4-- select 字段列表 from 表1 right [outer] join 表2 on 条件; 5select u.*, d.name from user u left outer join dept d on d.id = u.dept_id; -- 左外 6select d.*, u.name from user u right outer join dept d on d.id = u.dept_id; -- 右外 7select d.*, u.name from dept d left join user u on d.id = u.dept_id; -- 将右外改为左外
-
自连接:当前表与自身连接查询
1-- 自连接 2-- select 字段列表 from 表A 别名A join 表A 别名B ON 条件; 3-- 查询员工及其所属领导(员工和领导在同一表中), 将他们看做两张表,员工表和领导表 4select u1.name, u2.name from user u1 join user u2 on u1.manager_id=u2.id; 5-- 即便没有领导也查询 6select u1.name, u2.name from user u1 left join user u2 on u1.manager_id=u2.id;
联合查询
1-- 联合查询 union union all,将多次查询的结果合并起来, 列数必须保持一致
2-- select from union [all] select from
3-- 将年龄小于50岁 和 状态为1 的员工查询出来
4select * from user where age < 50 union all select * from user where status = 1; -- 结果有重复,直接将查询结果合并
5select * from user where age < 50 union select * from user where status = 1; -- 结果没重复,合并后去重
子查询
子查询, 在select中嵌套select,又称嵌套查询
这部分最重要的就是一部分一部分来, 查询一个阶段再将结果作为另一个阶段
1select * from t1 where column1=(select column1 from t2) 外部可以是select/update/insert/delete
子查询分类:
-
标量子查询(结果为单值), 操作符一般是> >= 之类
1-- 查询单个部门的员工信息,(select id from dept where name = '研发部')只有一条记录 2select * from user where dept_id=(select id from dept where name = '研发部');
-
列子查询, 操作符是in, not in, any/some, all
1-- any some是任意有一个满足即可 2-- all是子查询返回的列表的所有值都要满足 3-- 查询两个部门的员工信息 4select * from user where dept_id in (select id from dept where name = '研发部' or name = '市场部'); 5-- 查询比市场部所有人年龄都小的员工 6select * from user where age < all (select age from user where dept_id=(select id from dept where name = '总经办'));
-
行子查询, 操作符= <> in not in
1-- 查询和编号2员工的年龄与状态相同的员工,行元素对应 2select * from user where (age, status) = (select age, status from user where id=1); 3-- 表子查询,操作符in 4-- 查询与编号1,编号2员工的年龄和状态相同的员工,也是每一行对应一个,用in 5select * from user where (age, status) in (select age, status from user where id=1 or id=2); 6-- 查询年龄小于50的员工信息和部门信息 7select e.*, d.* from (select * from user where age < 50) e left join dept d on e.dept_id = d.id;
事务
准备:
1create table account(
2 id int auto_increment primary key ,
3 name varchar(50) ,
4 money int
5);
6insert into account(id, name, money) VALUES (null, 'lisan', 2000);
7insert into account(id, name, money) VALUES (null, 'wangsan', 2000);
8-- 恢复数据
9update account set money = 2000 where name = 'lisan' or name = 'wangsan';
10-- 查询lisan余额
11select * from account where name='lisan';
12-- 转账操作
13update account set money = money - 1000 where name = 'lisan';
14update account set money = money + 1000 where name = 'wangsan';
管理事务
方式一
1-- 查看事务的提交方式
2select @@autocommit;
3-- 设置为手动提交, 0为手动,1为自动
4set @@autocommit = 0; -- 设置为手动
5set @@autocommit = 1; -- 设置为自动
6-- 操作
7update account set money = money - 1000 where name = 'lisan';
8update account set money = money + 1000 where name = 'wangsan';
9-- 提交事务
10commit;
11-- 回滚事务, 如果出错了就回滚
12rollback;
方式二
1-- 开启事务
2start transaction;
3begin;
4-- 提交事务
5commit;
6-- 回滚
7rollback;
事务的四大特性ACID
- 原子性atomicity: 不可分割的最小操作,一起成功,一起失败
- 一致性consistency: 事务完成,数据保持一致状态,比如余额是一定的
- 隔离性isolation: 数据库提供的隔离机制, 事务不受外部并发操作影响独立环境执行
- 持久性durability: 事务一旦提交或回滚, 对数据的改变是永久的
并发事务问题
- 脏读:事务读取来另一个事务没有提交的数据
- 不可重复读: 事务先后读取同一数据,两次读取的数据不同
- 幻读:查询时没有这行数据,但是插入时又存在这行数据
事务的隔离级别
- read uncommitted 脏读会出现 不可重复读会出现 幻读会出现 性能最高
- read committed(oracle默认) 脏读不会出现
- repeatable read(mysql默认) 脏读,不可重复读不会出现
- serializable 脏读,不可重复读,幻读不会出现 性能最差
事务隔离级别越高,数据越安全,性能越低.
1-- 查看事务隔离级别
2select @@transaction_isolation;
3-- 设置事务隔离级别
4-- set [session|global] transaction isolation level {read uncommitted|read committed|repeatable read|serializable};
5set session transaction isolation level serializable ;
6set session transaction isolation level repeatable read ;
模拟并发问题
模拟脏读
1-- 模拟脏读
2-- 窗口1
3set session transaction isolation level read uncommitted ;
4start transaction ;
5select * from account; -- 这时候可以看到lisan减了1000块,就是因为可以读到未提交的(read uncommitted)
6-- 窗口2
7start transaction ;
8update account set money = money - 1000 where name='lisan';
模拟不可重复读
1-- 模拟不可重复读
2-- 窗口1
3set session transaction isolation level read committed ;
4start transaction ;
5select * from account; -- 在commit之前查询
6select * from account; -- 在commit之后查询, 可以查询到变更的.
7commit ;
8-- 窗口2
9start transaction ;
10update account set money = money + 1000 where name='lisan';
11commit ;
模拟幻读
1-- 模拟幻读
2-- 窗口1
3set session transaction isolation level repeatable read ;
4start transaction ;
5select * from account where id=3; -- 1查不到
6insert into account(id, name, money) VALUES (null, 'zangsan', 3000); -- 4这边后执行,发现插入不了
7select * from account where id=3; -- 5再次查询,还是没有, 这是因为已经解决了不可重复读了.
8-- 窗口2
9start transaction ;
10insert into account(id, name, money) VALUES (null, 'zangsan', 3000); -- 2这边先insert
11commit ; -- 3先提交,这时已经有这一行了.
索引
mysql体系结构: 连接池(授权认证), 服务层(sql接口,sql分析优化,函数执行), 引擎层(数据存储和提取), 存储层
存储引擎: 存储数据,建立索引,更新/查询数据等技术的视线方式. 存储引擎是基于表的而不是库.
1-- 查询建表语句,默认innodb
2show create table account;
3# CREATE TABLE `account` (
4# `id` int NOT NULL AUTO_INCREMENT,
5# `name` varchar(50) DEFAULT NULL,
6# `money` int DEFAULT NULL,
7# PRIMARY KEY (`id`)
8# ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
9
10-- 查询当前数据库支持的存储引擎
11show engines;
12-- 创建表,指定引擎为MyISAM
13create table my_myisam(
14 id int auto_increment primary key,
15 name varchar(50)
16)engine = MyISAM;
17-- 创建表,指定引擎为MEMORY
18create table my_memory(
19 id int auto_increment primary key ,
20 name varchar(50)
21)engine = Momory;
存储引擎的特点
-
innodb是mysql5.5之后默认的存储引擎, 特点:DML遵循ACID模型,支持事务; 行级锁,提高并发性能; 支持外键foreign key 文件:xxx.ibd, 每张表都会对应一个表空间文件,存储改变的表结构(frm,sdi),数据和索引. 逻辑存储结构:一个表空间TableSpace包含很多Segment段,一个段包含很多Extent区,一个区包含很多Page页,一个页包含很多Row行(行数据). 一个row包含trx id,roll pointer, col1, col2, 适用于并发条件下要求数据的一致性, 除了插入查询还有很多的更新删除操作
-
MyISAM, mysql早期的默认存储引擎. 不支持事务,不支持外键, 支持表锁, 不支持行锁,访问速度快 文件: sdi表结构信息,myd数据,myi索引 读操作和插入操作为主, 更新和删除较少时选用
-
Memory, 存储在内存,只能作为临时表或缓存 内存存放,访问速度快, hash索引 通常做缓存
innodb与MyISAM区别:innodb支持外键,是行锁,支持事务.MyISAM是表锁
索引
索引: 高效获取数据的数据结构(有序)
优点:提高检索效率,降低数据库io成本;通过索引进行排序,降低数据排序的成本
缺点:费空间,提高了查询效率但是降低了更新效率
索引结构
有B+Tree索引(常见), Hash索引(性能高,不支持范围查询),R-tree(空间索引),Full-text(全文索引)
二叉树:顺序插入时形成链表,层级深
红黑树:自平衡二叉树,但是数据量大的时候层级也深.
B-Tree(多路平衡查找), 从下面往上面走的.
B+Tree:相比B-Tree,所有的元素会出现的叶子结点(向上分裂的同时保留自己),叶子节点会形成单向链表
MySQL在B+Tree的基础上,将叶子节点的链表变为了双向循环链表,提高区间访问性能.
Hash索引:将键值换算成Hash值,映射在对应的槽位,存储在Hash表中.如果Hash冲突就增加链表.(就是HashSet原理), 只能用于对等比较= in,不能范围查询; 无法排序; 查询效率高. Memory支持hash索引.
索引分类
主键索引(primary, 只能有一个), 唯一索引(unique), 常规索引, 全文索引(fulltext)
根据存储形式分类:聚集索引(数据存储和索引一块,叶子结点保存行数据,必须要,只能有一个), 二级索引(数据和索引分开, 叶子结点是主键,可以多个)
索引选取规则:
- 如果存在主键, 主键索引就是聚集索引
- 没有主键, 选取第一个unique唯一索引作为聚集索引
- 都没有,Innodb自动生成rowid作为隐藏的聚集索引
如select * from user where name = ‘li’; 先从二级索引找主键,再从聚集索引找数据.这就是回表查询
索引语法
1-- 创建索引 create [unique|fulltext] index index_name on table_name (index_col_name,..)
2-- 查看索引 show index from table_name;
3show index from learndatabase.user;
4-- 删除索引 drop index index_name on table_name;
5-- 为name字段创建索引,该字段可能重复(常规索引)
6create index idx_user_name on user(name);
7-- 为phone创建唯一索引(唯一索引, 单列索引)
8create unique index idx_user_phone on user(phone);
9-- 为多个字段创建索引(联合索引),使用最频繁的字段放左侧(最左前缀原则)
10create index idx_user_name_phone_age on user(name, phone, age);
SQL性能分析
SQL执行频率
1-- show [session|global] status;
2-- 查看select, update, delete, insert等执行次数
3show global status like 'Com_______';
慢查询日志
慢查询日志: 记录执行时间超过指定参数long_time_query,默认10秒的日志
记录位置: /var/lib/mysql/localhost-slow.log
默认没有开启,在MySQL配置文件/etc/my.cnf配置, slow_query_log=1 1表示开, long_time_query=2 2秒
1-- 查看是否开启
2show variables like 'slow_query_log';
查看时间耗费
show profiles能够在做sql优化的时候帮助我们了解时间耗费到哪去了
have_profiling参数查看是否支持profile
1select @@have_profiling;
2-- 查看是否开启
3select @@profiling;
4-- 设置开启
5set profiling = 1;
6-- 查看耗时情况
7show profiles;
8-- 查看指定query_id的sql语句每个阶段的耗时情况
9-- show profile for query [query_id];
10show profile for query 3;
查看sql语句的执行计划
explain[/desc] 查看sql语句的执行计划, 如何执行的
1explain select * from learndatabase.user where age < 50;
- id字段:id相同,执行顺序从上往下, id越大越先执行
- select_type:SIMPLE简单表(不使用表连接或子查询),primary(主查询,即外层的查询), union(union中第二个或者后面的查询语句), subquery(select或where之后包含的子查询)
- type:连接类型. 性能由好到坏是NULL(不查任何表), system, const(唯一索引), eq_ref, ref(非唯一性索引), range, index(扫描索引), all(全表扫描)
- possible_key:可能用到的索引
- key:实际用到的索引
- key_len:使用索引的字节数
- rows:必须要执行查询的行数
- filtered:返回结果的行数占需读取行数的百分比,越大越好
索引使用
1-- 验证索引效率:先执行一条没有索引的sql,再创建索引再执行
2select * from tb_sku where sn='100001';
3create index idx_sku_sn on tb_sku(sn);
4select * from tb_sku where sn='100001';
最左前缀法则: 查询从索引最左列开始, 并且不跳过索引中的列; 如果跳过某列, 索引将部分失效(后面的字段失效)
1-- 假设创建了联合索引且顺序为profession,age,status,那么
2explain select * from user where profession='软件工程' and age=31 and status='0'; -- 会用到索引,所有字段都走索引
3explain select * from user where profession='软件工程' and age=31; -- 会用到索引,所有字段都走索引
4explain select * from user where profession='软件工程' -- 会用到索引,所有字段都走索引
5explain select * from user where age=31 and status='0'; -- 不会用到索引
6explain select * from user where profession='软件工程' and status='0'; -- 只有profession会用到索引
7explain select * from user where age=31 and profession='软件工程' and status='0'; -- 会用到索引,这个sql语句的顺序无关,只需要存在
范围查询: 联合索引中, 出现范围查询(<.>),那么范围查询右侧的列索引失效
1explain select * from user where profession='软件工程' and age<31 and status='0'; -- profession和age走索引,status不走
2explain select * from user where profession='软件工程' and age<=31 and status='0'; -- 都走,其实只有=那里都走
索引失效
1-- 索引列运算操作, 会导致索引失效
2explain select * from user where substring(phone, 10, 2) = '15'; -- 没用索引
3-- 字符串没加引号, 会导致索引失效
4explain select * from user where name = lisan;
5explain select * from user where profession='软件工程' and age=31 and status=0; -- status没有走索引
6-- 模糊查询, 如果仅仅是尾部模糊, 索引不会失效; 头部模糊匹配会失效
7explain select * from user where profession like '软件%'; -- 走索引
8explain select * from user where profession like '%软件'; -- 不走
9explain select * from user where profession like '%件%'; -- 不走
10-- or连接的条件: 前面列有索引,后面列没索引,那么索引都不会用到; 只有都有索引才会用到
11explain select * from user where phone='17712312310' or age=13; -- age没索引,所以没有用任何索引
12-- 数据分布影响: 如果MySQL评估使用索引比全表更慢,则不使用索引
13explain select * from user where phone>'17700000000'; -- 绝大部分数据都满足,这时就没用索引
14explain select * from user where phone is not null; -- 绝大部分都满足,没走索引
15explain select * from user where phone is null; -- 绝大部分都不满足,走索引
SQL提示
给mysql说想要什么索引(如果有多个可用的)之类,达到优化操作的目的
1-- 比如既有单个索引,又有联合索引的情况.
2-- use index: 建议用哪个索引 ignore index: 不用哪个索引 force index:必须用哪个索引
3explain select * from user use index(idx_user_pro) where profession='软件工程';
4explain select * from user ignore index(idx_user_pro) where profession='软件工程';
5explain select * from user force index(idx_user_pro) where profession='软件工程';
覆盖索引
查询使用索引, 并且需要返回的列, 在该索引中已经全部能够找到, 这样只需要查联合索引即可, 不需要回表查询
所以需要减少select *的使用, 这样可以提高效率
1-- 如现有索引profession, age, status的联合索引
2explain select id, profession, age, status from user where profession='软件工程' and age=31 and stauts='0'; -- 效率高
3explain select id, profession, age, status, name from user where profession='软件工程' and age=31 and stauts='0'; -- 效率低
前缀索引
索引很长的字符串,让索引变得很大,查询时浪费大量磁盘IO. 此时将字符串的一部分前缀建立索引,节约索引空间
1create index idx_xxxx on table_name(column(n));
前缀长度的选取: 根据选择性决定. 选择性是不重复的所有值比上记录总数, 选择性越高效率越高, 唯一索引的选择性是1.
一般是根据需求选择, 不需要必须选择性为1, 0.9的效率也可以.
1select count(distinct email) / count(*) from tb_user; -- 全部字段的选择性
2select count(distinct substring(email, 1, 5)) / count(*) from tv_user; -- email字段截取前5个的选择性
在查询的时候, 查索引, 然后回表对比是否一致; 继续查索引链表的下一个看看是否一致. 这是以时间换空间.
总结
单列索引和联合索引的选择: 存在多个查询条件, 考虑针对查询字段建立索引时, 建议建立联合索引
索引设计原则:
- 数据量大(一百万), 查询频繁建立索引
- 常作为查询条件where, 排序order by, 分组group by操作的字段建立索引
- 尽量选择区分度高(效率高)的列作为索引, 尽量建立唯一索引
- 字符串类型字段,长度长的话可以建立前缀索引
- 尽量使用联合索引. 查询时联合索引很多时候可以覆盖索引, 节省存储空间, 避免回表
- 控制索引数量, 索引越多, 维护代价越大, 影响增删改效率
- 如果索引列不能存储NULL值, 创建表时使用NOT NULL约束它. 当优化器知道每列是否包含null值时, 它可以更好确定那个索引最有效用于查询
SQL优化
插入数据, insert优化
-
批量插入, 一次性插入500-1000条, 特别大量的插入分成多个insert
1insert into tb_test values (1,'tom'), (2,'cat'), (3,'jerry');
-
手动事务提交
1start transaction ; 2insert into ; 3insert into ; 4commit ;
-
主键顺序插入
-
使用load指令(大批量数据)
1①连接mysql加参数--local-infile 2mysql --local-infile -u root -p 3②设置全局参数local_file为1, 开启从本地加载文件导入数据的开关 4set global local_infile =1; 5③执行load指令. fields terminated by ','表示列之间的分隔符, lines terminated by '\n'表示行数据的分隔符 6load data local infile '/root/sql1.log' into table 'tb_user' fields terminated by ',' lines terminated by '\n';
主键优化
页分裂: 当一个数据插入到页已经满的地方时, 就会页分裂. 这时复制后50%到另一个页里面, 然后将数据插入另一个页, 最后再修改页指针执行新的页
页合并: 当删除一行记录, 并没有真正地被物理删除, 只是标记删除并且它的空间允许其他记录使用. 当删除页达到MERGE_THRESHOLD(默认页50%), Innodb开始寻找最靠近的页 看看是否可以将两个页合并以优化空间使用.
主键设计原则:
- 尽量降低主键长度(辅助索引叶子节点挂的都是主键, 会导致空间占用很多)
- 插入数据尽量顺序插入, 选择使用AUTO_INCREMENT自增主键
- 尽量不要使用UUID做主键或者其他自然主键, 如身份证
- 业务操作避免对主键的修改.
order by 优化
using filesort: 通过表的索引或全表扫描, 读取满足条件的数据行, 然后在排序缓冲区sort buffer中完成排序操作.
所有不是通过索引直接返回排序结果的排序都叫filesort排序, 覆盖索引是前提.
using index: 通过有序索引顺序扫描直接返回有序数据, 不需要额外排序, 操作效率高.
1explain select id, age, phone from tb_user order by age, phone -- 没有索引时, using filesort
2
3create index idx_user_age_phone_aa on tb_user(age, phone); -- 创建两个升序索引
4explain select id, age, phone from tb_user order by age, phone; -- 有索引时, using index
5
6explain select * from tb_user order by age, phone; -- 没用索引, using filesort, 这时不是覆盖查询了, 回表了.
7
8explain select id, age, phone from tb_user order by age desc, phone desc; -- 反向利用索引, using index
9
10explain select id, age, phone from tb_user order by age asc, phone desc; -- 没有用到索引 using filesort
11
12create index idx_user_age_phone_ad on tb_user(age asc, phone desc); -- 创建一个升序,一个降序索引
13explain select id, age, phone from tb_user order by age asc, phone desc; -- 用到索引 using index
OrderBy优化:
- 根据排序字段建立合适的索引, 多字段排序时, 遵循最左前缀法则.
- 尽量使用覆盖索引
- 多字段排序, 一个升序一个降序, 此时需要注意联合所以在创建时的规则, show index中的collation就是排序规则,A升D降
- 如果不可避免的出现filesort, 大数据量排序时, 可以适当增大排序缓冲区大小sort_buffer_size(默认256k)
group by 优化
1explain select profession, count(*) from tb_user group by profession; -- 没有查询的时候, 用临时表
2create index idx_user_pro_age_sta on tb_user(profession, age, status); -- 创建联合索引
3explain select profession, count(*) from tb_user group by profession; -- 用了联合索引
4explain select age, count(*) from tb_user group by age; -- 违反最左前缀法则, 用到临时表
5explain select profession, age, count(*) from tb_user group by profession, age; -- 用联合索引
6explain select age, count(*) from tb_user where profession='软件工程' group by age; -- 用到联合索引, 最左前缀
limit优化
limit 1000000, 10 此时MySQL需要排序前1000010条记录, 仅仅返回1000000-1000010的记录, 其他丢弃
一般分页查询时, 通过创建覆盖索引能够较好地提高性能, 可以通过覆盖查询+子查询的形式进行优化
1select * from tb_sku limit 1000000, 10; -- 19s
2select t.* from tb_sku t, (select id from tb_sku order by id limit 1000000, 10) a where t.id = a.id; -- 11s
3
count优化
MyISAM引擎把一个表总行数存在了磁盘上, 因此执行count(*)的时候直接返回这个数, 效率很高
Innodb执行count(*)需要把数据一行行从引擎里面读出来, 累积计数.
-
count()是一个聚合函数, 对于返回结果集, 一行行判断. 如果count函数的参数不是null, 累计数+1, 否则不加. 最后返回累计值
-
count(*): Innodb并不会把全部字段取出来, 专门做了优化, 不取值, 服务层直接按行进行累加.
-
count(1): Innodb遍历整张表, 但不取值, 服务层对于返回的每一行, 放一个数'1’进去, 直接按行进行累加. 当然其他数都可以
-
count(主键): Innodb遍历整张表, 把每一行的主键id取出来, 返回服务层, 服务层拿到主键直接按行进行累加(主键没有null)
-
count(字段): 没有not null的字段, 取出来之后到服务层, 服务层需要判断是否是null, 不为null计数累加
-
count(字段): 有not null的字段, 取出来之后到服务层, 服务层直接按行进行累加
效率: count(*) ≈ count(1) > count(主键) > count(字段)
update优化
Innodb的行锁是针对索引加的锁, 不是针对记录加的锁, 使用的条件一定要有索引不然就是表锁; 该索引不能失效, 否则行锁变为列锁
1-- 使用主键索引
2update student set no='20010101' where id=1; -- 此时事务加的行锁, 对其他并发事务修改其他行没有影响
3update student set no='20010101' where name='韦一笑'; -- 当name字段没有索引时,此时加的是表锁, 对表的其他行均不能使用update变更
视图
1-- 视图
2-- 介绍: 视图是一种虚拟存在的表. 视图中的数据并不在数据库中实际存在, 行和列数据来定义自定义视图的查询中使用的表, 并且是在使用视图时动态生成的
3-- 作用: 1.简单:简化用户对数据的理解, 也可以简化他们的操作. 那些被经常使用的查询可以被定义为视图
4-- 2.安全:数据库可以授权,但不能授权到数据库特定的行和列上. 通过视图用户只能查询和修改他们所能见到的数据
5-- 3.数据独立: 视图可帮助用户屏蔽真实表结构带来的影响.
6
7-- 创建: create [or replace] view 视图名称[(列名列表)] as select语句 [with [cascaded|local] check option]
8create or replace view stu_v_1 as select id,name from student where id<=10;
9-- 查询: 查看视图创建语句 show create view 视图名称;
10-- 查看视图数据 select * from 视图名称....;
11show create view stu_v_1;
12select * from stu_v_1;
13select * from stu_v_1 where id<3;
14-- 修改: 方式一:create [or replace] view 视图名称[(列名列表)] as select语句 [with [cascaded|local] check option]
15-- 方式二:alter view 视图名称[(列名列表)] as select语句 [with [cascaded|local] check option]
16create or replace view stu_v_1 as select id,name,no from student where id<=10;
17alter view stu_v_1 as select id,name from student where id<=10;
18-- 删除: drop view [if exist] 视图名称 [,视图名称]...
19drop view if exists stu_v_1;
20
21
22-- 检查选项 with [cascaded|local] check option
23-- 当使用with check option子句创建视图, mysql会通过视图检查正在更改的每个行, 如插入,删除,更新, 使其符合试图定义
24-- mysql允许基于另一个视图创建视图, 它还会检查依赖视图中的规则以保持一致性. 为了确定检查范围提供了两个选项cascaded和local, 默认cascaded
25-- 没有check的视图, 可以随便插入数据
26create or replace view stu_v_1 as select id, name from student where id <= 10;
27insert into stu_v_1 values (30, 'tom'); -- 可以插入
28-- 普通的with check option的作用:
29create or replace view stu_v_1 as select id, name from student where id <= 10 with local check option ;
30insert into stu_v_1 values (6,'tom'); -- 可以插入,满足要求
31insert into stu_v_1 values (13,'tom'); -- 不能插入, 不满足<=10的要求
32-- cascaded选项, 级联的意思, 就是向下的视图不管有没有check选项都要检查
33create or replace view stu_v_1 as select id, name from student where id <= 20; -- 视图1, 没有check
34create or replace view stu_v_2 as select id, name from stu_v_1 where id >= 10 with cascaded check option ; -- 视图2, 级联检查
35insert into stu_v_2 values (7,'tom'); -- 不行, 不满足视图2的要求
36insert into stu_v_2 values (30,'tom'); -- 不行, 不满足视图1的要求
37insert into stu_v_2 values (15,'tom'); -- 可以
38-- 再在基础上新增一个视图3, 这时cascaded不会影响上层视图
39create or replace view stu_v_3 as select id, name from stu_v_2 where id >= 15;
40insert into stu_v_3 values (11,'tom'); -- 满足视图1和2的要求, 可以. 视图3的要求没有check所以条件无所谓
41insert into stu_v_3 values (17,'tom'); -- 满足要求, 可以
42insert into stu_v_3 values (28,'tom'); -- 不满足
43-- LOCAL选项: 它只影响本视图, 不会影响上下.
44
45-- 视图的更新: 要使视图更新, 视图中的行与基础表的行之间必须存在一对一的关系
46-- 视图包含以下任一项 视图不可更新
47-- 1.聚合函数或窗口函数(sum, min, max count等)
48-- 2.distinct
49-- 3.group by
50-- 4.having
51-- 5.union或union all
52
53
54-- 视图案例
55-- 1.为了保证数据库安全, 开发人员在操作tb_user表时, 只能看到用户的基本字段,屏蔽手机号和邮箱两个字段
56create view tb_user_view as select id, name, profession, gender, status, create_time from tb_user;
57select * from tb_user_view;
58-- 2.查询学生选修的课程(三表联查),为了简化操作,定义视图
59create view tb_stu_cource_view as select s.name student_name, s.no student_no, c.name course_name from student s, student_course sc, course c where s.id=sc.studentid and sc.courseid=c.id;
存储过程/函数/触发器
1-- 存储过程: 事先经过编译并存储在数据库中的一段SQL语句的集合, 调用存储过程可以简化应用开发人员的很多工作,
2-- 减少数据在数据库和应用服务器之间的传输,对提高数据处理的效率有好处
3-- 存储过程的思想很简单, 就是数据库SQL语言层面的代码封装与重用
4-- 特点: 封装复用; 可以接收参数, 返回数据; 减少网络交互提升效率
5
6
7-- 创建存储过程
8# create procedure 存储过程名称([参数列表])
9# begin
10# -- sql语句
11# end;
12create procedure p1()
13begin
14 select count(*) from user;
15end;
16-- 如果在命令行中创建存储过程, 需要指定sql语句的结束符, 通过delimiter
17-- delimiter $$ 设置结束以$$结束
18# create procedure p1()
19# begin
20# select count(*) from user;
21# end$$
22
23-- 调用
24-- call 名称([参数]);
25call p1();
26-- 查看存储过程
27-- select * from information_schema.ROUTINES where ROUTINE_SCHEMA='数据库名字'; -- 查询指定数据库存储过程及状态信息
28-- show create procedure 存储过程名称; -- 查询某个存储过程定义
29select * from information_schema.ROUTINES where ROUTINE_SCHEMA='bbs';
30show create procedure p1;
31-- 删除
32-- drop procedure if exists 存储过程名称;
33drop procedure if exists p1;
34
35
36-- 变量
37
38-- 系统变量是是MySQL服务器提供的, 不是用户定义的, 属于服务器层面. 分为全局变量(global)和会话变量(session)
39-- 全局变量在所有MySQL会话都有效, 会话变量只在当前会话有效
40-- 查看系统变量, 默认都是session
41-- 查看所有系统变量 show [global|session] variables;
42-- 通过模糊匹配查看变量 show [global|session] variables like '....';
43-- 查看指定变量 select @@[global|session]系统变量名;
44show variables ;
45show session variables ;
46show global variables ;
47show variables like 'auto%';
48show global variables like 'auto%';
49select @@autocommit;
50select @@session.autocommit;
51select @@global.autocommit;
52
53-- 设置系统变量, MySQL服务重启后, 所设置的全局变量会失效, 要想不失效,可以在/etc/my.cnf中配置
54-- set [session|global] 系统变量名=值;
55-- set @@[session|global] 系统变量名=值;
56set session autocommit = 0;
57set global autocommit = 0;
58
59-- 用户定义变量: 不用提前声明, 用的时候直接用@变量名
60-- 作用域是当前会话, 其他会话用不了.
61-- 赋值, 推荐使用:=, 因为=可以作为比较运算符
62-- set @var_name =expr [,@var_name=expr]...;
63-- set @var_name :=expr [,@var_name:=expr]...;
64-- select @var_name :=expr [,@var_name :=expr] ...;
65-- select 字段名 into @var_name from 表名;
66set @caonima='cnm';
67set @mygender:='男', @myhobby:='java';
68select @cnm:='red';
69select count(*) into @usercount from learndatabase.user;
70-- 使用
71-- select @var_name;
72select @caonima;
73select @mygender, @myhobby, @usercount;
74select @abc; -- 没声明赋值,结果是null
75
76-- 局部变量: 需要定义的在局部生效的变量, 访问需要declare声明. 可用作存储过程的局部变量和输入参数
77-- 局部变量的范围是在其内声明的begin and块.
78-- 声明, 没有@符号了
79-- declare 变量名 变量类型 [default...];
80-- 变量类型: int, bigint, char, varchar, date, time等
81-- 赋值, 没有@符号.
82-- set 变量名=值;
83-- set 变量名:=值;
84-- select 字段 into 变量名 from 表名;
85create procedure p2()
86begin
87 declare stu_count int default 0;
88 select count(*) into stu_count from learndatabase.user;
89 select stu_count;
90end;
91call p2();
92
93
94-- 流程控制结构
95-- if语法
96# if 条件1 then
97# ...
98# elseif 条件2 then
99# ...
100# else
101# ...
102# end if;
103create procedure p3()
104begin
105 declare score int default 59;
106 declare result varchar(10);
107
108 if score >= 85 then
109 set result:='优秀';
110 elseif score >=60 then
111 set result:='及格';
112 else
113 set result:='不及格';
114 end if;
115
116 select result;
117end;
118
119call p3();
120
121-- 参数
122-- 类型: IN 作为输入的参数, 需要调用时传值, 此为默认
123-- OUT 作为输出的参数, 该参数可以作为返回值
124-- INOUT 既可以作为输入, 也可以作为输出的参数
125# create procedure 存储过程名称([in/out/inout 参数名 参数类型])
126# begin
127# -- sql
128# end;
129-- 判断成绩的存储过程
130create procedure p4(in score int, out result varchar(10))
131begin
132 if score >= 85 then
133 set result:='优秀';
134 elseif score >=60 then
135 set result:='及格';
136 else
137 set result:='不及格';
138 end if;
139end;
140call p4(90, @result);
141select @result;
142
143-- 将传入的200分制的分数, 换算成100分制的分数
144create procedure p5(inout score double)
145begin
146 set score:=score * 0.5;
147end;
148set @score:=199.8;
149call p5(@score);
150select @score;
151
152-- case
153-- 语法1
154# case case_value
155# when when_value1 then statement_list1 -- 当case_value=value1时执行when之后的sql语句
156# [when when_value2 then statement_list2]...
157# [else statement_list] -- 都不满足时执行的sql语句
158# end case;
159-- 语法2
160# case
161# when search_condition1 then statement_list1 -- 当search_condition1为true时执行
162# [when search_condition2 then statement_list2]...
163# [else statement_list] -- 都不满足时执行的sql语句
164# end case;
165-- 根据传入的月份,判断月份所属的季节
166create procedure p6(in month int)
167begin
168 declare result varchar(10);
169 case
170 when month >= 1 and month <= 3 then set result:='第一季度';
171 when month >= 4 and month <= 6 then set result:='第二季度';
172 when month >= 7 and month <= 9 then set result:='第三季度';
173 when month >= 10 and month <= 12 then set result:='第四季度';
174 else set result:='错误输入!';
175 end case;
176 select concat('输入的月份为', month, ', 它所属的季度为: ', result);
177end;
178call p6(4);
179
180-- while循环: 有条件的循环, 满足条件才循环
181# while 条件 DO
182# sql...;
183# end while;
184-- 计算1累加到n
185create procedure p7(in count int)
186begin
187 declare i int default 1;
188 declare res int default 0;
189 while i <= count do
190 set res := res + i;
191 set i := i + 1;
192 end while;
193 select res;
194end;
195
196create procedure p7(in count int)
197begin
198 declare res int default 0;
199 while count > 0 do
200 set res := res + count;
201 set count := count - 1;
202 end while;
203 select res;
204end;
205drop procedure p7;
206call p7(100);
207
208-- repeat循环: 满足循环退出
209# repeat
210# sql...;
211# until 条件; -- 当条件为真,退出循环
212# end repeat;
213-- 1加到n
214create procedure p8(in count int)
215begin
216 declare res int default 0;
217 repeat
218 set res:=res+count;
219 set count:=count-1;
220 until count <0 end repeat;
221 select res;
222end;
223call p8(10);
224
225-- loop循环: 简单循环, 如果不增加退出循环条件那么就是死循环, 它配合leave和iterate使用
226-- leave: 退出循环
227-- iterate: 必须用在循环中, 作用是跳过当前循环剩下语句进入下循环
228# [begin_label:] loop -- 指定标识label
229# sql...;
230# leave label; -- 退出循环
231# iterate label; -- 跳到下次循环
232# end loop [end_label];
233-- 实现1累加n
234create procedure p9(in count int)
235begin
236 declare res int default 0;
237 loop_label: loop
238 set res := res + count;
239 set count := count - 1;
240 if count <= 0 then
241 leave loop_label;
242 end if;
243 end loop loop_label;
244 select res;
245end;
246call p9(100);
247-- 计算1-n的偶数累加
248create procedure p10(in count int)
249begin
250 declare res int default 0;
251 if count % 2 = 1 then
252 set count:= count -1;
253 end if;
254 odd_sum:loop
255 set res := res + count;
256 set count := count - 2;
257 if count <= 0 then
258 leave odd_sum;
259 end if;
260 end loop odd_sum;
261 select res;
262end;
263
264create procedure p10(in count int)
265begin
266 declare res int default 0;
267 odd_sum: loop
268 if count <= 0 then
269 leave odd_sum;
270 end if;
271 if count % 2 = 1 then
272 set count := count - 1;
273 iterate odd_sum;
274 end if;
275 set res := res + count;
276 set count := count - 1;
277 end loop odd_sum;
278 select res;
279end;
280
281drop procedure p10;
282call p10(10);
283
284
285-- 游标: 存储查询结果集的数据类型, 在存储过程和函数中可以使用游标对结果集进行循环处理
286-- 声明游标: declare 游标名称 cursor for 查询语句;
287-- 打开游标: open 游标名称;
288-- 获取游标记录: fetch 游标名称 into 变量[,变量];
289-- 关闭游标: close 游标名称;
290-- 案例: 根据传入的参数uage, 查询用户表中所有年龄小于uage的用户姓名name和专业profession, 然后将name和profession插入新表
291create procedure p11(in uage int)
292begin
293 declare uname varchar(100);
294 declare ugender char(1);
295 declare u_cursor cursor for select name, gender from learndatabase.user where age <= uage;
296
297 drop table if exists tb_user_gender;
298 create table if not exists tb_user_gender(
299 id int primary key auto_increment,
300 name varchar(100),
301 gender char(1)
302 );
303 open u_cursor;
304 while true do -- 这里不知道什么时候结束
305 fetch u_cursor into uname, ugender;
306 insert into tb_user_gender values (null, uname, ugender);
307 end while;
308 close u_cursor;
309
310end;
311
312-- 条件处理程序(异常): 定义在流程控制结构执行过程中遇到的问题时相应的处理步骤
313-- declare handler_action handler for condition_value[,condition_value] ... statement;
314-- handler_action: continue:继续执行当前程序, exit:终止执行当前程序
315-- condition_value: sqlstate sqlstate_value: 状态码, 如02000
316-- sqlwarning: 所有以01开头的sqlstate代码的简写
317-- not found: 所有以02开头的sqlstate代码的简写
318-- sqlexception: 所有没有被sqlwarning和not found捕获的sqlstate代码的简写
319create procedure p11(in uage int)
320begin
321 declare uname varchar(100);
322 declare ugender char(1);
323 declare u_cursor cursor for select name, gender from learndatabase.user where age <= uage;
324 declare exit handler for sqlstate '02000' close u_cursor; -- 满足状态码为02000触发退出, 退出时将游标关闭
325 -- declare exit handler for not found close u_cursor;
326
327 drop table if exists tb_user_gender;
328 create table if not exists tb_user_gender(
329 id int primary key auto_increment,
330 name varchar(100),
331 gender char(1)
332 );
333 open u_cursor;
334 while true do -- 现在最后一次循环后, 下面的语句会报错, 被handler抓取退出.
335 fetch u_cursor into uname, ugender;
336 insert into tb_user_gender values (null, uname, ugender);
337 end while;
338 close u_cursor;
339
340end;
341call p11(30);
342
343
344-- 存储函数: 有返回值的存储过程, 参数只能是in类型的.
345# create function 函数名称([参数列表])
346# returns type [characteristic...]
347# begin
348# --sql
349# return ... ;
350# end;
351-- characteristic说明:
352-- determinstic:相同的输入总是产生相同的结果
353-- no sql:不包含sql语句
354-- reads sql data:包含读取sql的语句, 不包含写入数据的语句
355-- 案例: 1累加到n
356create function fun1(n int)
357returns int deterministic
358begin
359 declare res int default 0;
360 while n>0 do
361 set res:=res+n;
362 set n:=n-1;
363 end while;
364 return res;
365end;
366
367
368-- 触发器: 在insert/update/delete之前或之后, 触发并执行触发器中定义的sql语句集合
369-- 确保数据完整性, 日志记录, 数据校验等操作.
370-- 使用别名old和new来引用触发器中发生变化的记录内容
371-- insert触发器:new表示将要或已经新增的数据
372-- update触发器:old表示修改之前的数据, new表示将要或已经修改后的数据
373-- delete触发器:old表示将要或已经删除掉的数据
374-- 现在只支持行级触发器(每行触发一次), 不支持语句级触发器(不管影响多少行,只执行一次)
375
376-- 创建触发器
377# create trigger trigger_name
378# before/after insert/update/delete
379# on tbl_name for each row -- 行级触发器
380# begin
381# trigger_statement;
382# end;
383-- 查看
384show triggers ;
385-- 删除
386# drop trigger [schama_name.]trigger_name; -- 没有指定数据库名默认为当前数据库
387
388-- 需求, 记录tb_user表数据变更日志, 插入到日志表user_logs中, 包含增加,修改,删除
389create table user_logs(
390 id int(11) not null auto_increment,
391 operation varchar(20) not null ,
392 operation_time datetime not null ,
393 operation_id int(11) not null ,
394 operation_params varchar(500) ,
395 primary key (`id`)
396)engine =innodb default charset =utf8;
397
398-- 定义三个触发器
399--insert触发器
400create trigger insert_trigger
401 after insert on learndatabase.user for each row
402begin
403 insert into user_logs(id, operation, operation_time, operation_id, operation_params) values
404 (null, 'insert', now(), new.id, concat('插入的数据内容: ', new.id, new.name, new.gender, new.gender));
405end;
406show triggers ;
407drop trigger insert_trigger;
408insert into learndatabase.user values (null, 'rurushu', 38, 1, '男', 3, 5);
409-- update触发器
410create trigger update_trigger
411 after insert on learndatabase.user for each row
412begin
413 insert into user_logs(id, operation, operation_time, operation_id, operation_params) values
414 (null, 'update', now(), new.id, concat('更新前数据: ', old.id, old.name, old.gender, old.gender,
415 '更新后数据: ', new.id, new.name, new.gender, new.gender));
416end;
417-- delete触发器
418create trigger delete_trigger
419 after insert on learndatabase.user for each row
420begin
421 insert into user_logs(id, operation, operation_time, operation_id, operation_params) values
422 (null, 'delete', now(), old.id, concat('删除前数据: ', old.id, old.name, old.gender, old.gender));
423end;
锁
分类: 全局锁, 表级锁, 行级锁.
全局锁
全局锁: 对整个数据库实例加锁, 加锁后处于只读状态, 后续的DML写, DDL, 已经更新操作的事务提交语句都被阻塞
使用场景: 做全库的逻辑备份 从而获取一致性视图, 保证数据完整性…
1flush tables with read lock ;
2mysqldump -uroot -proot learndatabase > learndatabase.sql
3unlock tables ;
特点:
- 主库备份, 备份期间不能执行更新操作, 业务基本停摆
- 从库备份, 备份期间不能执行主库同步过来的二进制日志, 导致主从延迟
Innodb中, 备份时加上参数–single-transaction参数来完成不加锁的一致性数据备份(快照读实现)
1mysqldump --single-transaction -uroot -proot learndatabase > learndatabase.sql
表级锁
表级锁: 发生锁冲突的概率最高, 并发度最低. 应用在MyISAM,Innodb,BDB等存储引擎
分类: 表锁, 元数据锁, 意向锁
表锁
表锁: 分为表共享读锁(read lock, 所有客户端只能读不能写), 表独占写锁(write lock, 只有自己能读能写, 别的客户端不能读不能写)
1-- 加锁
2lock tables 表名... read/write
3-- 释放锁
4unlock tables 或者 客户端断开连接
元数据锁
元数据锁: 系统自动控制不需要显示使用, 访问一张表自动加上. 主要是维护表元数据的一致性,有活动事务时不可对元数据进行写入操作
元数据大概就是表结构
在MySQL5.5后, 对表进行增删改查,加MDL读锁(共享);当对表结构变更时,加MDL写锁(排它), 这两个锁是冲突的.
比如两个终端,事务1使用了select加MDL读锁,那么事务2使用alter就会等待,因为这两个锁是排斥的,加不了MDL写锁.
1-- 查看元数据锁
2select object_type, object_schema, object_name, lock_type, lock_duration from performance_schema.metadata_locks;
意向锁
意向锁: 为了避免DML在执行时加的行锁和表锁的冲突(不能同时加),Innodb中引入了意向锁, 使得表锁不用检查每行数据是否加锁,使用意向锁减少表锁的检查
意向共享锁(IS):由select… lock in share mode添加; 与表锁共享锁(read)兼容,与表锁排它锁(write)互斥.
意向排它锁(IX):由insert,update,delete,select…for update添加; 与表锁共享锁和表锁排它锁都互斥, 这样就可以保证数据一致性.
意向锁之间不会互斥
1-- 例子:
2select * from learndatabase.user lock in share mode ; -- 这句话加上行锁共享锁和意向共享锁
3lock tables learndatabase.user read; -- 这时可以加表共享读锁, 因为这两个锁兼容; 都可以读嘛
4lock tables learndatabase.user write; -- 这时不能加表独占写锁, 互斥的; 因为如果允许的话, 别个就可以改数据了,读的东西不一致.
1begin ; -- 客户端1开启事务
2update learndatabase.user set gender='女' where id=1; -- 加上行锁和意向排它锁
3lock tables learndatabase.user read; -- 客户端2加锁, 不行
4lock tables learndatabase.user write; -- 客户端2加锁, 不行
1-- 查看意向锁及行锁的加锁情况
2select object_schema, object_name, index_name, lock_type, lock_mode, lock_data from performance_schema.data_locks;
行级锁
行级锁: 发生锁冲突的概率最低, 并发度最高, 应用在Innodb中.
Innodb的数据是基于索引组织的, 行锁是往索引上的索引项加锁, 不是对记录加锁
行级锁分为三类:
- 行锁(record lock): 锁定单行记录, 防止其他事务进行update, delete. 在rc(read committed),rr(repeatable read)隔离级别下都支持
- 间隙锁(gap lock): 锁定记录与记录之间的间隙(不含记录), 确保索引记录间隙不变, 防止其他事务在这个间隙insert,产生幻读, 在rr隔离级别下都支持.
- 临键锁(next-key lock): 行锁和间隙锁的组合, 同时锁住数据,并锁住数据前面的间隙gap.在rr隔离级别下支持, 防止幻读
这些锁和事务的隔离级别相辅相成.
行锁
Innodb实现了两种类型行锁:
- 共享锁(s): 允许一个事务去读一行, 阻止其他事务获得相同数据集的排它锁, commit后就释放了(防不可重复读, 脏读)
- 排它锁(x): 允许获取排它锁的事务更新数据, 阻止其他事务获得相同数据集的共享锁和排它锁.
1-- insert, update, delete 获得排它锁, 系统自动加锁不需要控制
2-- select 不加任何锁
3-- select ... lock in share mode 共享锁
4-- select ... for update 排它锁
5select * from learndatabase.user where id=1; -- id=1这一行的共享锁, 那么其他事务对这一行数据不能获得排它锁
6update stu set name='lei' where id=1; -- 排它锁
针对唯一索引进行检索时, 对已存在的记录进行等值匹配, 将会自动优化为行锁.
Innodb的行锁是针对索引加的锁, 不通过索引条件检索数据, 那么Innodb将对表的所有记录加锁, 行锁升级为表锁.
update优化的原理就是这个:
1update stu set name ='lei' where name='lily'; -- 事务1,第8行数据. 加的表锁.因为此时name没有索引,没有走索引
2update stu set name='lei' where id=1; -- 事务2, 即便是第一行数据也不能更改, 因为这时已经是表锁了.
1-- 查询行锁
2select object_schema, object_name, index_name, lock_type, lock_mode, lock_data from performance_schema.data_locks;
间隙锁/临键锁
1-- 1. 索引上给等值查询(唯一索引), 给不存在的记录加锁, 优化为间隙锁
2update stu set age = 10 where id = 5; -- 事务1 表中只有3和8,之间没有数据, 这时加间隙锁, 锁住(3.8]这个空隙
3insert into stu values (7,'lili', 8); -- 事务2 这时再往里插入, 插入不了被锁住了.
4
5-- 2. 索引上的等值查询(普通索引), 向右遍历时最后一个值不满足查询条件时, next-key lock退化为间隙
6-- 这里age字段就不是唯一索引可能有多个相同的值, 那么它会加11及后面17之间的锁(11后就是17)
7create index index_idx_stu_age on stu(age);
8select * from stu where age = 11 lock in share mode ; -- 事务1, 首先对3记录加共享行锁S,其次还要对17加临键锁,锁住本身和其间隙, 防止幻读
9
10-- 3. 索引上的范围查询(唯一索引), 会访问到不满足条件的第一个值为止.
11select * form stu where id > 30; -- 唯一索引范围查询. 假设查出来的有31 50 ,那么会给31加S锁, 还会给50加临键锁, 还会将50到正无穷也加S锁
12-- 间隙锁的目的是防止其他事务插入间隙, 间隙锁可以共存, 一个事务采用的间隙锁不会阻止另一个事务在同一间隙采用间隙锁.
Innodb引擎/事务原理/MVCC
1-- Innodb引擎
2-- 逻辑存储结构
3-- 表空间tablespace: ibd文件, 一个mysql实例可以对应多个表空间, 用于存储记录, 索引等数据.
4-- 段segment: 分为数据段, 索引段, 回滚段. Innodb是索引组织表, 数据段就是B+Tree的叶子结点, 索引段即为B+Tree的非叶子节点. 段用来管理多个Extent区
5-- 区extent: 表空间的单元结构, 每个区的大小为1m, 默认情况下, Innodb存储引擎页大小为16k, 即一个区中一共有64个连续的页.
6-- 页page: 是Innodb存储引擎磁盘管理的最小单元, 每个页的大小默认是16kb. 为了保证页的连续性, Innodb存储引擎每次从磁盘申请4-5个区
7-- 行row: Innodb存储引擎数据是按行进行存放的. 行的数据结构:
8-- Trx_id: 每次对每条记录进行改动时, 都会把对应的事务id赋值给trx_id隐藏列
9-- Roll_pointer: 每次对每条记录进行改动时, 都会把旧的版本写入到undo日志中, 然后这个隐藏列就相当于一个指针, 可以用它找到该记录修改前的信息
10-- col1, col2, ...
11
12
13-- 架构
14-- 内存架构
15-- 1.buffer pool: 缓冲池是主内存的一个区域, 里面可以缓存磁盘上经常操作的真实数据, 在执行增删改查操作时, 先操作缓冲池中的数据(没有数据就从磁盘加载并缓存)
16-- 然后再以一定频率刷新到磁盘, 从而减少磁盘io, 加快处理速度.
17-- 缓冲池以页page为单位, 底层采用链表数据结构管理page, page分为三类:
18-- free page: 空闲page, 没有使用过
19-- clean page: 被使用page, 数据没有被修改过
20-- dirty page: 脏页, 被使用page, 数据被修改过, 页中数据与磁盘数据产生了不一致
21-- 2.change buffer: 更改缓冲区(针对非唯一二级索引页), 在执行DML语句时, 如果这些数据page没有在buffer pool中, 不会直接操作磁盘, 而会将数据变更
22-- 存在更改缓冲区change buffer中, 在未来数据被读取时, 再将数据合并恢复到buffer pool中, 然后再将合并后的数据刷新到磁盘中
23-- 好处: 与聚集索引不同, 二级索引通常是非唯一的, 并且以相对随机的顺序插入二级索引. 同样删除和更新可能会影响索引树种不相邻的二级索引页, 如果每次都
24-- 操作磁盘, 会造成大量磁盘io, 有了change buffer之后, 我们可以在缓冲池中进行合并处理, 减少磁盘io
25-- 3.adaptive hash index: 自适应哈希索引, 用于优化对buffer pool数据的查询. Innodb存储引擎会监控对表上各索引页的查询, 如果观察到hash索引可以
26-- 提升速度, 则建立hash索引, 称之为自适应hash索引.
27-- 自适应哈希索引, 无需人工干预, 是系统根据情况自动完成
28-- 参数adaptive_hash_index
29-- 4.log buffer: 日志缓冲区, 用来保存要写入到磁盘中的log日志数据(redo log, undo log), 默认大小16MB, 日志缓存区的日志会定期刷新到磁盘中. 如果
30-- 需要更新,插入,删除许多行的事务, 增加日志缓冲区的大小可以节省磁盘io
31-- 参数innodb_log_buffer_size: 缓冲区大小, innodb_flush_log_at_trx_commit: 日志刷新到磁盘的时机(0:每秒将日志写入并刷新到磁盘; 1:日志在每次事务提交时写入并刷新到磁盘; 2:日志在每次事务提交后写入,并每秒刷新到磁盘一次)
32
33
34-- 磁盘架构
35-- 这些文件系统里面都可以找得到.
36-- System tablespace: 系统表空间是更改缓冲区的存储区域, 如果表是在系统表空间而不是每一个表文件或通用表空间中创建的, 它也可能包含表和索引数据
37-- 参数:innodb_data_file_path
38-- File-per-table tablespace: 每个表的文件表空间包含单个Innodb表的数据和索引, 并存储在文件系统上的单个数据文件中.
39-- 参数:innodb_file_per_table
40-- general tablespace: 通用表空间, 需要通过create tablespace语法创建通用表空间, 在创建表时, 可以指定该表空间.
41create tablespace ts_name add datafile 'file_name.ibd' engine =innodb;
42create table a(id int primary key auto_increment, name varchar(10))engine =innodb tablespace ts_name;
43-- undo tablespace: 撤销表空间, mysql实例在初始化时会自动创建两个默认的undo表空间(undo_001,undo_002)初始大小为16M, 用于存储undo log日志.
44-- temporary tablespace: 临时表空间, Innodb使用会话临时表空间和全局临时表空间,存储用户创建的临时表等数据.
45-- doublewirte buffer files: 双写缓冲区, Innodb将数据页从buffer pool刷新到磁盘前, 为了保证数据安全, 先将数据写入双写缓冲区文件中, 便于系统异常时恢复数据.
46-- redo log: 重做日志, 用来实现事务持久性. 该日志由两部分组成:重做日志缓冲(redo log buffer, 内存中), 重做日志文件(redo log, 磁盘中, ib_logfile0, ib_logfile1),
47-- 当事务提交之后会把所有修改信息都会存到该日志中, 用于在刷新脏页到磁盘时发生错误进行数据恢复. 这文件循环写, 事务提交里面的数据没有意义了
48
49
50-- 后台线程
51-- 1. master thread: 核心后台线程, 负责调度其他线程, 还负责将缓冲池中的数据异步刷新到磁盘中, 保持数据一致性, 还包括脏页的刷新, 合并插入缓存, undo页的回收
52-- 2. io thread: Innodb引擎中大量使用了aio来处理io请求, 可以极大提升数据库性能, 而io thread主要负责这些io请求的回调.
53-- read thread: 默认4个, 负责读操作
54-- write thread: 默认4个, 负责写操作
55-- log thread: 默认1个, 负责将日志缓冲区刷新到磁盘
56-- insert buffer thread: 默认1个, 负责将写缓冲区的内容刷新到磁盘
57-- 查看Innodb引擎的状态信息, 里面有线程信息
58show engine innodb status ;
59-- 3. purge thread: 用于回收事务已经提交的undo log. 事务提交后undo log可能不用了,用它回收.
60-- 4. page cleaner thread: 协助master thread刷新脏页到磁盘的线程, 减轻master thread的压力, 减少阻塞.
61
62
63-- 事务原理
64-- 事务的原子性, 一致性, 持久性由redo log和undo log保证
65-- 事务的隔离性由锁和MVCC(多版本并发控制)保证
66
67-- redo log(保证持久性)
68-- redo log: 重做日志, 记录的是事务提交时数据页的物理修改, 用来实现事务持久性. 该日志由两部分组成:重做日志缓冲(redo log buffer, 内存中), 重做日志文件(redo log, 磁盘中),
69-- 当事务提交之后会把所有修改信息都会存到该日志中, 用于在刷新脏页到磁盘时发生错误进行数据恢复. 这文件循环写, 事务提交, 里面的数据没有意义了
70-- 流程: 对缓冲区buffer pool数据进行增删改, 这时页面变成脏页, 同时将增删改的数据记录在redolog buffer记录数据页物理变化, 事务提交时将log buffer刷新到磁盘文件中ib_logfile0/1
71-- 之后脏页刷新到磁盘. 如果脏页刷新到磁盘失败了, 可以通过redo log恢复
72
73-- undo log(保证原子性)
74-- 回滚日志, 用于记录数据被修改前的信息, 作用包含两个:提供回滚和MVCC
75-- undo log和redo log记录物理日志不一样, 它是逻辑日志. 可以认为当delete一条记录时, undo log中会记录一条对应的insert记录, 反制一番, 当update一条记录时,
76-- 它记录一条对应相反的update记录. 当执行rollback时, 就可以从undo log中逻辑记录读取到相应的内容并进行回滚
77-- undo log销毁: undo log在事务执行时产生, 事务提交时, 并不会立即删除undo log, 因为这些日志可能还用于mvcc
78-- undo log存储: undo log采用段的方式进行管理和记录, 存放在rollback segment回滚段中, 内部包含1024个undo log segment
79
80
81
82-- MVCC
83-- 基本概念
84-- 当前读: 读取的是记录的最新版本, 读取时还要保证其他并发事务不能修改当前记录, 会对读取的记录进行加锁.
85-- 如select ... lock in share mode(共享锁), select ... for update , update, insert, delete(排它锁)都是一种当前读
86begin;
87select * from learndatabase.user; -- 在别的事务commit之前和之后去读到的都是同样的数据, 可重复读
88select * from learndatabase.user lock in share mode ; -- 别的事务commit之后可以读取到最新的数据.
89-- 快照读: 简单的select(不加锁)就是快照读, 快照读, 读取的是记录数据的可见版本, 有可能是历史数据, 不加锁, 是非阻塞读
90begin;
91select * from learndatabase.user; -- 在别的事务commit之前去读,生成一个快照
92select * from learndatabase.user; -- 在别的事务commit之后读, 依旧读的是之前的快照
93-- read committed: 每次select都生成一个快照读
94-- repeatable read: 开启事务后第一个select语句才是快照读的地方.
95-- serializable: 快照读退化成当前读, 每次读取都会加锁.
96-- MVCC: 维护一个数据的多个版本, 使得读写操作没有冲突, 快照读为mysql实现MVCC提供了一个非阻塞读功能, 还需要依赖与数据库记录中的三个隐式字段, undo log日志, readview.
97
98
99-- 实现原理
100-- 记录中的隐藏字段
101-- 1.DB_TRX_ID: 最近修改事务ID. 记录插入这条记录或最后一次修改该记录的事务ID(事务ID是从1自增的)
102-- 2.DB_ROLL_PTR: 回滚指针. 指向这条记录的上一个版本, 用于配合undo log, 指向上个版本.
103-- 3.DB_ROW_ID: 隐藏主键. 当表结构没有指定主键时会生成该隐藏字段.
104-- 查看隐藏字段
105ibd2sdi user.ibd
106
107-- uodo log: 回滚日志, 在insert, update, delete的时候产生的便于数据回滚的日志
108-- 当insert时, 产生的undo log只在回滚时需要, 在事务提交后便被立即删除
109-- 当update, delete时, undo log不止在回滚时需要, 在快照读时也需要, 不会被立刻删除
110
111-- undo log版本链
112-- 不同事务或相同事务对同一条记录进行修改, 会导致该记录的undo log生成一条记录版本链表, 头部是最新的旧记录, 尾部是最早的旧记录.
113-- undo log记得是旧记录的所有东西, 里面就包括了DB_ROLL_PTR指针, 所以旧记录的该字段指向的是下一条旧记录
114
115-- readview: 读视图. 是快照读sql执行的MVCC提取数据的依据, 记录并维护系统当前活跃的事务(未提交的)id
116-- 包含四个核心字段:
117-- 1.m_ids:当前活跃的事物ID集合(没有commit的事务id)
118-- 2.min_trx_id: 最小活跃事物ID(m_ids中的最小值)
119-- 3.max_trx_id: 预分配事务ID, 当前最大事务ID+1(事务是自增的, 下一个就是+1)
120-- 4.creator_trx_id: readview创建者的事务ID.
121-- 版本链数据访问规则: (trx_id代表当前这条记录对应的事务的id, 就是db_trx_id)
122-- 1. trx_id == creatr_trx_id时 可以访问该版本. (说明数据是当前事务更改的)
123-- 2. trx_id < min_trx_id时 可以访问该版本 (说明事务已经提交了)
124-- 3. trx_id > max_trx_id时 不可以访问该版本. (说明该事务是在readview生成后才开启, 这里感觉可以取等)
125-- 4. min_trx_id <= trx_id <= max_trx_id且trx_id不在m_ids中 可以访问该版本 (说明数据已经提交)
126-- 不同的隔离级别, 生成readview的时机不同:
127-- read committed: 在事务中每次执行快照读时生成readview
128-- repeatable read: 仅在事务第一次执行快照读时生成readview, 后续复用该readview
129
130-- 原理分析(rc级别)
131-- read committed: 在事务中每次执行快照读时生成readview
132-- 简单来说就沿着undo log日志, 从新往旧每一条记录的db_trx_id去和规则作对比, 如果都不能访问则沿着版本链往下继续找直到满足条件为止.
133-- 原理分析(rr级别)
134-- 与rr唯一不同就是两次查询的readview不同罢了. 所以rc两次查询可能不同, rr都是一样的. 可重复读嘛