基础篇
基础语法
window下的启动和停止,默认开机自启
DDL数据定义()
数据库操作
查询所有数据库: SHOW DATABASES;
查询当前数据库: SELECT DATABASE();
创建数据库: CREATE DATABASE [ IF NOT EXISTS ] 数据库名 [ DEFAULT CHARSET 字符集] [COLLATE 排序规则 ];
删除数据库: DROP DATABASE [ IF EXISTS ] 数据库名;
使用数据库: USE 数据库名;
注意事项
- UTF8字符集长度为3字节,有些符号占4字节,所以推荐用utf8mb4字符集
表操作
查询当前数据库所有表: SHOW TABLES; 查询表结构: DESC 表名; 查询指定表的建表语句: SHOW CREATE TABLE 表名;
创建表:
只有正数用unsigned
固定长度用char
修改表
添加字段: ALTER TABLE 表名 ADD 字段名 类型(长度) [COMMENT 注释] [约束]; 例:ALTER TABLE emp ADD nickname varchar(20) COMMENT ‘昵称’;
只修改数据类型: ALTER TABLE 表名 MODIFY 字段名 新数据类型(长度);
修改字段名和字段类型: ALTER TABLE 表名 CHANGE 旧字段名 新字段名 类型(长度) [COMMENT 注释] [约束]; 例:将emp表的nickname字段修改为username,类型为varchar(30) ALTER TABLE emp CHANGE nickname username varchar(30) COMMENT ‘昵称’;
删除字段: ALTER TABLE 表名 DROP 字段名;
修改表名: ALTER TABLE 表名 RENAME TO 新表名
删除表: DROP TABLE [IF EXISTS] 表名;
删除表,并重新创建该表: TRUNCATE TABLE 表名;
总结
DML 数据操作(增删改)
修改表中某个字段的所有数据时,不加where条件即可
delete的删除是删整条记录。 update的修改 是针对某个字段
总结
DQL数据查询
关键字:select(查询) 编写顺序如下:
基础查询(查询字段)
查询多个字段:
SELECT 字段1, 字段2, 字段3, ... FROM 表名;
SELECT * FROM 表名;
设置别名:
SELECT 字段1 [ AS 别名1 ], 字段2 [ AS 别名2 ], 字段3 [ AS 别名3 ], ... FROM 表名;
SELECT 字段1 [ 别名1 ], 字段2 [ 别名2 ], 字段3 [ 别名3 ], ... FROM 表名;
去除重复记录:
SELECT DISTINCT 字段列表 FROM 表名;
转义:
SELECT * FROM 表名 WHERE name LIKE '/_张三' ESCAPE '/'
/ 之后的_不作为通配符
条件查询
条件查询
关键词:where
聚合函数与分组查询
聚合函数作用于某一列,将一列数据聚合为一个整体。所有null不参与聚合函数计算
先where过滤,再把过滤后的group by 分组,再把分组后的结果 having过滤,过滤后的结果再用select配合聚合函数、以及分组的字段显示。
where是分组之前的过滤,having是对分组的结果进行过滤。
排序查询
** **
分页查询limit
想查第二条开始的10条记录,应该输 select * from table limit 1,10; limit 起始位置,条数
页表下标从0开始。
DQL查询的编写、执行顺序
总结
DCL管理用户以及访问权限
管理用户
查询用户:
USE mysql;
SELECT * FROM user;
创建用户:
CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码';(创建的用户只能在设定的主机上访问,想在任意主机上访问时把主机名变成%)
修改用户密码:
ALTER USER '用户名'@'主机名' IDENTIFIED WITH mysql_native_password BY '新密码';
删除用户:
DROP USER '用户名'@'主机名';
mysql默认有一个数据库 mysql,里面有一张user表显示当前的用户:
host是主机名(意味着该用户可以在哪些主机上访问到数据库),user是用户名。
例子:
– 创建用户test,只能在当前主机localhost访问
create user ‘test’@‘localhost’ identified by ‘123456’;
– 创建用户test,能在任意主机访问
create user ‘test’@‘%’ identified by ‘123456’;
create user ‘test’ identified by ‘123456’;
– 修改密码
alter user ‘test’@‘localhost’ identified with mysql_native_password by ‘1234’;
– 删除用户
drop user ‘test’@‘localhost’;
注意事项
主机名可以使用 % 通配
权限控制
常用权限:
查询权限:
SHOW GRANTS FOR ‘用户名’@‘主机名’;
授予权限:
GRANT 权限列表 ON 数据库名.表名 TO ‘用户名’@‘主机名’;
撤销权限:
REVOKE 权限列表 ON 数据库名.表名 FROM ‘用户名’@‘主机名’;
注意事项
多个权限用逗号分隔
授权时,数据库名和表名可以用 * 进行通配,代表所有
总结
Mysql内置函数
字符串函数
常用函数:
CONCAT(s1, s2, …, sn) 字符串拼接,将s1, s2, …, sn拼接成一个字符串
LOWER(str) 将字符串全部转为小写
UPPER(str) 将字符串全部转为大写
LPAD(str, n, pad) 左填充,用字符串pad对str的左边进行填充,达到n个字符串长度
RPAD(str, n, pad) 右填充,用字符串pad对str的右边进行填充,达到n个字符串长度
TRIM(str) 去掉字符串头部和尾部的空格
SUBSTRING(str, start, len) 返回从字符串str从start位置起的len个长度的字符串,字符串下标从1开始不是0
REPLACE(column, source, replace) 替换字符串
数值函数
由于会隐式转换,所以数值也可以使用字符串函数
日期函数
对于date_add的例子:
对于datediff(date1, date2) 求的是date1 - date 2
流程控制函数
’ ’ 并不是null
总结
字段约束
约束是字段上的规则,限制字段上的数据。在创建表,修改表的时候添加约束(可添加多个约束)
例如当建某个表要求如下时:(约束之间用空格就行了)
创建表的时候如下:
外键约束
左表的dept_id就是右表的id,两张表通过外键建立连接,建立外键关联后,删除一张表会影响另一张表,从而保证数据的一致性和完整性。
拥有外键的是子表, 外键关联的表是父表。
比如上面的两张表,要在员工表添加外键约束:
如果是在建表的时候就设置外键:
可以在添加外键约束设置删除/更新父表对应字段时,子类会发生的事情。
多表查询
多表关系
一对多: 部门跟员工, 通过在员工表添加一个外键
多对多: 学生跟课程, N对N,中间表至少包含两个外键,分别关联双方的主键。
一对一:用户跟用户详细信息, 用于单表拆分,将一张表的基础信息放在一张表,详细信息放在另一张表,在任意一方添加外键并且设置外键为唯一(unique,这样父表的一条记录只能唯一对应子表相应字段的一条记录)
多表查询
select的时候多写一个表。
内连接 (查询两表交集)
通过连接条件,消除掉无用的字段。
表起了别名后就不能用原名了。 inner 可以省略不写
外连接(完全包含左表或右表的数据)
左外连接 : 完全包含左表的信息
右外连接: 完全包含右表的信息
自连接(同一张表复合,可以是内连接也可以是外连接)
比如下图,要查Emp表中每个人对应的领导是谁,原来没有领导字段,但是有managerID字段,因此可以把managerID 当作连接,两张emp表连接起来。自连接一定要起别名。
联合查询union(把多次查询结果合并起来)
要求联合查询多张表的列数、字段类型要一致
使用union all将两次查询结果直接合并(不去重)
使用union 会去重
子查询
select嵌套
标量子查询(内层select返回的是单个值):
列子查询(内层select返回的是多个值,一列)
行子查询(内层select返回的是多个字段,一行)
表子查询(内层select返回的是一个表,多行多列)
总结
练习
事务
一组操作的集合,事务把一组操作整体向系统提交或撤销。 事务会先发生,如果出错了可以回滚,没出错再提交。
事务默认是自动提交的,即select @@autocommit 结果是1.修改成为0,即手动后。提交事务需要commit才能生效。
设置提交模式为事务有两种方式:
方式1:直接设置事务为手动提交
方式2 :开启事务
事务四大特性ACID
ACID:原子性、一致性、隔离性、持久性
原子性:事务是最小操作单元,里面的操作要么集体成功,要么集体失败
一致性:事务完成时,所有数据保持一致(不能发生混乱)
隔离性:并发操作时,事务不会受影响
持久性:事务一旦提交或回滚,持久化到磁盘中
事务并发问题
事务并发问题:多个事务并发执行时出现的脏读、不可重复读、幻读的问题
脏读:A事务读到了B事务未提交的数据就是脏读。 事务A修改数据,但是还没有提交。 而事务B去读数据,读到的是A修改过后的数据,但是此时A还没有提交。这就是脏读。
**不可重复读:**A事务的两次读取同一数据的操作中间,事务B修改并提交了数据。导致事务A两次读取结果不一致
**幻读:**A 事务查询id为1的数据发现不存在,此时B事务插入id为1的数据,A事务再插入id为1的数据时发现id为1已经存在,因为id是主键,主键唯一性导致插入失败。 这就是幻读。
事务的隔离级别
隔离级别能解决事务并发问题
默认可重复读级别:解决了脏读、不可重复读(读的时候不允许别人插入)
设置的时候如果加了session关键字,则只在当前用户对话框生效。最后一个叫串行化隔离(只允许一个事务先执行)。隔离级别越高,数据越安全,但是性能越低。
总结
总结
进阶篇SQL优化
体系结构如图:
引擎层是规划数据的存储方式,索引就在这层。 而真正存数据是在存储层。
存储引擎(表类型)
存储引擎是数据库的核心。 是存储数据、建立索引、更新/查询数据 的实现方式。 存储引擎基于表,不是基于库。所以也叫表类型,一个数据库下的不同表可以指定不同的存储引擎。
mysql5.5之后默认引擎是innodb。 通过 show engines可以查看当前数据库支持的存储引擎,创建表的时候可以指定存储引擎,不指定的话默认是innodb
innodb引擎
Mysql5.5后的默认引擎。 特点
8.0之前的表结构存在.frm,之后版本存在sdi。sdi存在表空间文件ibd中。每张表都有一个.ibd空间文件,包含了表结构、数据、索引。参数表示是多张表共用一个表空间文件还是一张表对应一个表空间文件。 默认为打开即一张表对应一个.ibd文件。
innodb的逻辑存储结构:
表空间,段,区,页,行。 区空间为1M,页大小为16k,因此一个区最多存64页。
myisam引擎
早期的默认引擎,表锁,速度快
文件3个
memory引擎
数据放到内存,支持哈希索引,文件只有.sdi表结构文件。速度很快。
三大引擎区别
总体来说就是innodb有3大特点。
总结
索引(重点)
索引是一种帮助mysql高速查询数据的一种数据结构。 数据库系统除了维护数据还需要维护索引。
没索引的时候是全表扫描。
优缺点:
优点:提高检索效率,降低排序成本。 缺点:索引占用空间,更新时要更新索引。
索引结构
平时说的索引都是指b+树索引
B+树索引中,每个节点都是一个页大小为16K,所有数据都存储在叶子节点上,叶子节点通过单链表连接。
而mysql的B+树是优化过的,叶子节点是双向链表。
哈希索引
只能用于对比索引(=,in),不支持范围查询,无法利用索引进行排序,查询效率高,不出现哈希冲突时一次检索就行。
innodb引擎不支持哈希索引,但是有一种
为什么innodb使用B+树索引?
索引分类
创建字段的时候设置唯一字段,自动会生成唯一索引。
聚集索引与二级索引(SQL优化的重点)
聚集索引的叶子节点放的是这一行数据。而二级索引叶子节点上放的是key(建立索引的时候写的字段是什么二级索引叶子上放的就是什么). 以及主键
比如 select * from user where name = 'Arm';
arm不是主键,因为select * 要全部数据,而二级索引只有name和主键id,那么先去二级索引B+树,找到叶子上Arm对应的主键id。 因为select * 要查询一行的数据,所以拿着主键id再去聚集索引上去找一行的数据。先去二级索引上找主键id再到聚集索引上找行数据的过程叫做回表查询
innodb主键索引的B+tree高度有多高?
一个节点就是一页,也就是16K,一个节点上要存key以及指针。假如key为n个,那么指针是n+1个,Innodb指针大小为6字节,假设key大小为8字节。那么n8 + (n+1)6 = 161024,那么一个节点(一页)可以指向n=1170个节点 那么高度为3的时候,叶子节点能存数据的大小就是11701170*16 =21939856的大小。二千多W字节的数据也就占了3层。
索引的语法
create [索引类型] index 索引名 on 表名 (字段1,字段2...);
不指定索引类型的时候,默认为常规索引。 要创建联合索引的时候,字段写多个就行了。
SQL性能分析(慢查询日志、profile详情)
SQL优化主要针对查询,而优化主要就是优化索引。 SQL的性能分析是看哪个操作用的多,为优化提供思路
查看增删改查的执行频率(看哪个操作用的最多)
**show [session/global] status 'com______'**
(7个下划线)
慢查询日志(执行后性能记录在这里)
记录 哪些查询语句的执行时间超过了设定的值,从而知道要优化什么。慢查询日志默认不开启(show variables like 'slow_query_log’可以查看是否开启) ,在配置文件中可以开启慢查询日志。
在Ubuntu中,直接在Mysql中输入
然后输入show variables like 'slow%';
可以看到慢查询日志存放在哪里。
profile详情(可查每条SQL语句的耗时,以及耗时主要在哪部分)
通过show profiles
可以查看语句耗时。 通过show profile for query ID
可以查看一个查询语句中的详细耗时情况,ID是show profiles后出现的一列query_ID,每条查询都有自己的ID。
通过select @@have_profiling;
查看当前数据库支不支持查看耗时
通过select @@profiling
查看是否已经开启profile
默认profiling是关闭的,通过 set profiling =1
开启show profiles;
后如下图:
explain执行计划(SQL优化的重点)
在select之前加上 explain或者desc
就可以看到这条select查询的计划。重点关注type
在多表查询或者有子查询的时候 这个ID反应了查询的先后顺序
优化的时候尽量往NULL这端优化。根据主键或唯一索引查询时是const,使用非唯一索引进行查询时 是ref类型,all是全表扫描,index表示用了索引,但是对索引树整个扫描。
索引使用原则(高效原则)
查询某个没有索引的列的数据时速度很慢,建立索引后查询就很快了。使用explain可以查看有没有用到索引。
联合索引遵循最左前缀法则
如果是联合索引,要遵守最左前缀法则。即查询时,最左边的索引要存在(最左的那个放在哪个位置无所谓,只需要存在),并且不能跳过某列的索引,如果跳过了中间的索引,那么后面的索引也不会走了 。(可以理解成联合索引依赖于它左边的索引,所以最左边的一定要有)
比如这3个字段专业、年龄、信息 是联合索引。那么查询的时候就必须where 专业、年龄、信息。专业必须有,年龄跟信息可以没有。但是不能直接where 专业 信息,这样做的话就不会走信息的索引。
范围查询右边的索引失效
第一个索引中,status就不会走索引,因为在范围查询的右边。 第二个会走索引,因为是>=,有=就会走索引
运算会导致索引失效
这里使用了字符函数运算,导致不会走phone的索引
字符不加’ ’ 索引失效
条件的列是字符类型,如果不加’'则不会走索引
前模糊匹配导致索引失效
尾部的%模糊索引不会导致索引失效
用OR分割开的条件必须两者都有索引才会走索引
or前面有索引 后面没有索引,那么都不会走索引
数据分布决定走不走索引
有时候查询的数据是表中的绝大部分的数据,走全表扫描其实更快,此时数据库系统会自动走全表扫描而不走索引
SQL提示(有多个索引可用时,自定义走哪个索引)
比如有个联合索引(专业、年龄、信息),然后又有一个单列索引专业,此时不指定索引走哪个时,SQL优化器自动走联合索引。而SQL提示就是可以自己指定走哪个索引的语句。
在from后面写 use/ignore/force index(索引名)
。 use 是建议使用,SQL优化器自己决定
覆盖索引效率更高(不回表,一次性查到更快)
where后面不是主键时尽量少用select * ,因为select* 很容易发生回表查询,效率很低。
select的东西通过一次索引就能找到,不需要回表就是覆盖索引,比如下面这个:
name是个二级索引,因为select的是id以及name, 而name二级索引叶子上放的就是主键id以及name,因此不会回表(覆盖索引)
下面这个就不会覆盖索引了,因为gender不在二级索引表上,gender需要通过name二级索引到的id回表。
直接建立一个联合索引(id, username, password),这样通过一个二级索引就能查到不需要回表。
前缀索引
如果字段类型是字符串类,大文本字段时,对它建立索引的时候有时候需要很长的字符串,导致浪费磁盘IO,此时可以对字符串的前一部分建立索引。 只需要建立索引时在列字段名后标记一下长度就行 字段名(n)
那就会根据字符串的前n个建立索引。
n的值可以根据索引的选择性来决定。选择性表示该前缀索引的区分度, 选择性为1的时候说明该索引是唯一的。 选择性的计算:去重后的计数/总数。
比如下面这个图,就是用email的前5个字符建立的前缀索引。查找时,先在二级索引中找到lvbu6,然后拿着id回表到聚集索引,找到对应的行,看行信息的全eamil是不是跟where后面的email一致,如果一致才返回*所有数据。
单列索引和联合索引的选择
查询存在多个条件时,最好使用联合索引。因为在select查询多个时,即使where后面的条件列 有多个单列索引,SQL只会使用单个效率最高的单列索引,并不会多个单列索引都是用。比如:有phone跟name两个单列索引,查询时只是用了phone单列索引。 当然,有联合索引跟单列索引时,使用use index(联合索引名) 来推荐SQL使用联合索引
联合索引的内部结构:phone跟name的联合索引
索引设计原则
1、频繁查询超过100W数据的时候建立索引
3、性别这种就没必要建立索引,区分度不高
5、多使用联合索引,联合索引会覆盖索引,减少回表
索引总结
SQL优化
插入数据优化
insert优化: 批量插入、手动提交事务、主键顺序插入
批量插入:,每次insert都需要建立连接,所以最好一次插入多条500~1000比较好。
手动提交事务:默认事务是自动提交,不手动控制的话一条SQL语句就自动开启一次事务
主键顺序插入:主键顺序插入性能相比乱序插入更好(这在主键优化中解释)
大批量数据使用load插入数据:
最后一句中的by ','
意思本地文件中每个字段采用逗号分隔,每行数据使用'\n'
分割
主键优化
主键索引是聚集索引,叶子节点放的是行数据,非叶子节点只起到索引的作用。叶子节点顺序存放,所以数据也是跟着主键id顺序存放的。这种表数据根据主键顺序存放的表称为索引组织表(IOT)
主键的设计原则:
- 尽量降低主键长度:二级索引的叶子存主键id,二级索引有多个,如果主键id太长,很浪费磁盘IO
- 插入数据时,选择顺序插入,选择auto_increment自增主键(非自增容易页分裂、页合并)
- 尽量不使用自然主键比如身份证(自然逐渐没有递增规则,不是顺序插入)
- 避免对主键修改(改一次主键,其他的二级索引什么的全部都会更新)
页分裂
聚集索引叶子节点上存数据,数据跟id一起顺序存储在页上的(IOT),页大小为16K,每页至少包含2行数据(上面的图就是2行)。当顺序插入时:
当乱序插入:50插入时,因为叶子节点要保证顺序,而47后面的空间又放不下50了。此时1#页会从中间分裂开,把23、47、50放到3#页中,然后 1页的指针连接3页,3页再连接2页。
页合并
Innodb的删除是标记为可被其他数据使用,当一个页中有50%(这个阈值在建表时可以设置)被标记的时候就会发生页合并,这个页会找相邻能合并的页进行合并。
order by 排序优化
尽量使用using index 排序方式,也就是直接取排好序的索引对应的数据
排序有两种,using filesort先取数据,再到排序缓冲区排序。using index 直接通过有序的索引进行取,不额外排序,效率更高,因此要尽量优化为using index的排序。当然,能使用using index排序的前提是通过索引能直接取到数据(也就是必须是覆盖索引),不然会回表取到数据再排序。
假如 为table 添加了联合索引(phone, age),现在explain order by phone ,age ,那么在extra中会显示排序是using index。
- 此时如果
explain order by age, phone
那么会是using filesort,因为没有phone不在前面,没有最左前缀法则。 - 如果
explain order by phone desc ,age desc
此时会using index,虽然两个索引默认是asc的,但是只要排序的时候两者都指定倒序,这时数据库系统会反向扫描。 - 如果
explain order by phone asc, age desc
;那么会显示using index,using filesort, 因为建立索引的时候默认是asc升序,所以phone跟age索引是默认升序的,而代码要求是age desc所以age不会走索引排序。这可以通过再建立一个索引,指定 索引创建的顺序。 一升一降的情况不会触发反向扫描
group by 分组操作优化
尽量使用索引
索引对分组也有优化,所以分组的时候可以通过索引提高效率。分组的时候索引也需要符合最左前缀法则,比如有联合索引(phone,age),如果是 where phone = ‘14’ group by age, 这也是符合最左前缀法则的,因为where里面使用了最左的phone
limit 分页优化
使用覆盖索引+子查询优化
当我们用 select * …limit 1000000, 10 的时候,MySQL会先扫描满足条件的1000010行,扔掉前面的1000000行,返回后面的10行。所以offset越大的时候,扫描的行就越多,效率也就越慢了。
优化思路:覆盖索引+子查询。先取到limit1000000,10的 主键id,然后根据把这10条主键id建立一个表跟主表连接起来查询
count 优化
尽量使用count(*)
count(xx) xx不是null时,计数加1。所以Count实际上是记录不为Null的数据条数。count计数在不需要取值的情况下效率是最高的。
updata 优化
要根据索引字段去更新。
两个事务同时修改一个数据。比如两个事务针对name字段(name建立了索引)进行修改。 事务A修改name = '1’的数据,事务B修改name = '2’的数据,此时可以修改成功,因为对有索引的字段修改是行锁。而如果两个事务修改另一个字段phone, phone没有索引,那么两个事务就不能同时修改成功了,因为没有索引时事务之间会有表锁。 因此,updata要根据有索引字段进行更新
总结
视图/存储过程/触发器
视图views
视图不保存数据,在数据库不真实存在,是一种虚拟存在的表(因此可以像操作表那样操作视图)。视图只保存查询的SQL逻辑,数据其实是用的SQL逻辑查询到的数据。语句如下,数据来自as后面的查询. create [or replace] view 视图名[(列名)] as select ...[with [cascaded/local] check option]
视图的作用:1、简单。简化用户对数据的操作,把常用数据定义为视图,以后的操作就不用每次都指定条件了。2、安全,通过视图用户只能查询和修改视图中有的数据。 3、数据独立,当真实表结构发生变化的时候,视图只需要改变select语句中的字段名就行。
视图的增删改查
查像操作表一样查就行。 视图在跟基表数据是一一对应的时候才可以增加或者更新
检查选项: with (cascaded/local) check option
insert into 视图名 values(值) 时, 是插到真实的表中去的,如果不加检查选项,插入的数据不会检查是否符合视图的定义条件。
比如这里,视图要求<= 20, 但是插入30也能正常执行,插入后再后、select * from 视图却看不到30这条数据。因为插入视图不做检查,select from视图时 又要满足<=条件。
with cascaded check option 级联检查(向底层传递with检查)
视图可以有多层,即v2可以基于v1生成。with cascaded 只要碰到,前面基于的视图都要检查。
比如 v1 <= 10
v2 <= 20 with cascaded check option
v3 <=15
v3 基于v2 , v2基于v1 。 此时对v3插入17, v3不做检查,检查v2,条件符合, 检查v1,条件不符合,插入失败。
with local check option
cascaded 只要碰到,前面的视图都要检查。
而local 只要碰到,就检查当前层是否满足,前面的视图不管。
比如 v1 <= 10
v2 <= 20 with local check option
v3 <=15
v3 基于v2 , v2基于v1 。 此时对v3插入17, v3不做检查,v2的条件符合, v1不做检查,则插入成功。
案例
1、
2、将三表联查封装为视图,以后查询就不必再三表连接了
首先是三表联查的SQL
然后将这条多表联查封装到视图中
create view tb_user_couse_view as
此时提示 有多个列是name,修改一下别名:
那么以后想操作就不需要再那么复杂了,直接对视图操作
存储过程procedure/存储函数
存储过程是一段SQL语句集合,它们事先编译了存储在数据库中,调用存储过程可以简化数据在应用层与数据库之间的传输,从而提高效率。其实就是SQL语句的代码封装与复用
在一个逻辑中要多次操作数据库,比如.涉及到多次网络请求,如果在数据库层面将多个请求封装,当应用层要使用的时候直接调用P1,一次就行了。
在命令行中创建存储过程可能出现; 歧义。 命令行以; 为结束,因此SQL语句的; 跟end的;有歧义。因此可以通过delimiter
设置命令行的结束标记,比如delimiter
,那么
e
n
d
后面跟着
, 那么end后面跟着
,那么end后面跟着就意味end结束了。
系统变量/用户自定义变量/局部变量
用户自定义变量的作用域是当前连接(当前会话session),不需要提前声明,直接一个@变量名 ,就是使用自定义变量。
赋值推荐使用:= ,使用表中数据给变量赋值的时候 记得select … into @变量
set @abc ,没有声明也没有赋值,此时abc变量的值是null
局部变量
declare 变量名 类型 [default 值],作用域在bgein 跟end之间
procedure中—if
if 条件 then
xxxx
end if;
根据传入参数score, 通过if判断,传出到 用户自定义变量result
procedure中—case
case
when
when
end case;
procedure中—while循环(满足条件do)
while 条件 do
语句
end while;
procedure中—repeat循环(满足条件退出)
repeat
语句
until 退出条件
end repeat;
procedure中—loop循环(配合leave和iterate使用)
loop要通过leave才能退出循环。iterate表示进入下次循环。loop在SQL语句中如果没有leave就会死循环。
循环名:loop
SQL语句(中间包含leave和iterate)
end loop:循环名;
procedure中—数据类型–游标
用来装多列数据的一个结果集
在存储过程里面声明的变量只是列数据,如果想用变量装一行的数据是做不到的。
比如 declare ID int ; 再select id into ID from…; 这可以做到。
如果是select * into ID from … 就错了。 因为ID无法装*
而游标可以装多列的结果数据。
几个问题:
- 因为表的id设定为自增了,所以插入的时候可以插入null
- 游标的声明应该在变量的声明之后,上面图中我写反了
- while 后面的条件处理 写true是错误的,当游标取完了,没有内容取了,就会报错,后面介绍条件处理程序可以解决报错。
条件处理程序—handler(配合取cursor内容使用)
相当于异常处理,在流程控制结构中遇到了对应问题,会有对应的处理步骤。
declare aa handler for bb 步骤cc;
当碰到bb的时候,就触发aa,并且执行步骤cc
触发的aa 可以是exit终止当前程序,或者continue继续执行
触发的条件bb ,在下面有列出
此时再把上面游标中例子执行,即使是where true,执行到后面游标没有内容可以取了,此时会报错0200也就是Not found的一种,由于有条件处理程序,此时会exit程序,并且执行 close u_cursor;关闭游标
存储函数
存储函数是有返回值的存储过程,并且存储函数的参数只能是IN类型。
create function 存储函数名(参数表)
returns 类型名 描述特性 // 存储函数必须指定返回值类型,mysql8.0后需要指定描述特性,不然会报错
begin
—SQL 语句
—return …; // 存储函数必须有返回值
end;
存储函数的功能都可以使用存储过程实现,存储函数还必须要有返回值returns type以及描述特性,所以最好使用存储过程。
触发器trigger
跟表有关的一种对象,在表执行 insert/update/delete时会自动触发的一种东西。 本质是一块自定义的SQL语句。用来日志记录、数据校验。
触发器支持行级触发,不支持语句级触发。
行级触发:比如一句语句update5行数据,那就会触发5次。
语句级触发器:不管语句改了几行数据,只触发一次。
触发器带有OLD,NEW两个关键字,代表触发器之前,之后的数据。
create trigger 触发器名
之前/之后 触发器类型
on 表名 for each row // 行级触发器
begin
触发语法内容
end;
(id是主键,设置为自增了,因此insert时可以传null)
总结
锁
数据库中数据作为一种多用户共享的资源,为了确保一致性、有效性,也需要使用锁。
mysql的锁按粒度分为:全局锁(每次操作锁数据库中所有表)、表级锁(每次操作锁整张表)、行级锁(每次操作锁对应的行)。 锁在事务结束时释放。
全局锁(实现全库备份 )
加全局锁后,整个数据库的表都变为只读。 全局锁一般用于全库备份。
比如一个数据库有2张表 库存、订单,不加全局锁进行全库备份时,订单一边在生成,而库存数却不减少,导致数据不一致。 加了全局锁后,订单不允许新增,只读(不允许DML、DDL,只允许DQL),保证了数据的一致性。
演示:
加全局锁: flush tables with read lock;
全库备份指令:是命令行指令
解锁:unlock tables;
缺点:
不加锁的全库备份指令:
底层是通过数据快照实现的。
表级锁
每次操作锁定整张表。分为 表锁、元数据锁、意向锁
表锁
** 包括读锁、写锁**
客户端1对表1添加读锁,那么客户端1跟其他客户端对表1都能读,不能写(读锁是共享锁)
客户端1对表1添加写锁,那么客户端1对表1可以读写,其他客户端不能读,也不能写(写锁是排他锁)
共享锁之间兼容,排他锁跟共享锁以及排他锁都互斥。
元数据锁(MDL)
防止DML修改数据 跟DDL修改表结构产生冲突。 在表中有未提交的事务时(修改数据的事务没有提交的时候MDL元数据锁没有释放),不可以对元数据(表结构)进行操作。MDL锁是自动添加的
对一张表增删改查时,自动加MDL读锁(共享锁)。当对表结构修改时,自己加MDL写锁(排他锁)
读锁跟读锁之间是兼容的,跟互斥锁是不兼容的。 互斥锁跟任何MDL锁都是互斥的
共享锁是兼容的,意思select时,自动加的读锁是共享锁,其他客户端此时也可以select。
alter table 修改表结构时 是MDL写锁,排他锁。 此时其他客户端select之类的是不行的,因为MDL写锁互斥。
意向锁
解决了innodb行锁存在时想加表锁的问题。意向锁相当于表的一个标志,当想对一张表加表锁前,会检查想加的锁跟意向锁是否互斥。
举个例子: 线程A 对某行数据加了行锁,此时线程B想对表加表锁,那么就会逐行检查行锁是否跟表锁兼容,这样效率太低了。 因此意向锁的概念出现了。当对表中加行锁时,会自动对表加一个意向锁,当其他内容想给表加表锁的时候,直接跟意向锁进行匹配,看是否兼容。
比如客户端A select * from tbale (对行加行锁,并且对表加意向共享锁)。 此时客户端B lock tables tble read; (给table表加read读锁), 此时是可以加上的。 因为IS 跟表锁读锁是兼容的
而如果是 lock tables tble write . 此时客户端B给table上的表写锁是加不上去的,因为跟IS意向锁互斥。
行级锁
行级锁是innodb的特点之一。 粒度最小,发生锁冲突概率低,并发度高。 行锁是基于索引项实现的,而不是记录本身,使用索引才会有行锁,没使用索引加的是表锁。
行锁有3种。
行锁锁单行数据,防止其他事务进行修改和删除。
间隙锁锁中间距离(不含两端),防止在A B中间insert数据。解决幻读现象,间隙锁可以共存
临建锁,前两种的结合,既不准改数据,也不准在中间插入
行锁
增删改是排他锁(互斥),普通select不加锁,select …lock in share mode 加共享锁
事务开启后,读数据的时候(通过索引读数据)使用 临建锁,根据索引的不同,临建锁会变成不同的锁。
1中的唯一索引 比如主键索引就是唯一索引。
2中,因为普通索引不是唯一,比如插入18,它插入的时候会往前后找其他18,所以它会在18跟右边第一个不为18的值之间加上一个间隙锁,防止中间被其他人插入数据。
3 范围查询时候加的锁: 比如 select * from table where age >= 25, 那么会给25加行锁,给25到表中最大值加间隙锁, 给表中最大值到正无穷大加间隙锁
总结
性能上考虑锁的粒度越小效率越高,间隙锁是为了解决幻读的问题
Innodb引擎
innodb逻辑存储结构
表空间,段,区,页,行。 区大小是1M,包含64个页,一个页大小16K。
表空间(ibd文件),一个mysql实例可以对应多个表空间
段:分为数据段、索引段、回滚段。数据段就是B+树的叶子节点,索引段即B+树的非叶子节点。段管理多个区
页:innodb管理磁盘的最小单元,默认大小16K,为了保证页的连续性,Innodb每次从磁盘申请4~5个区
行:innodb存储的数据就是按行存储的
innodb的底层架构
包括4个缓冲区、磁盘区、4组线程实现缓冲区跟磁盘区之间的交互
innodb内存架构----4个缓冲区
一台mysql专门的服务器,80%内存分配给内存架构的缓冲区
buffer pool 内存缓冲区
innodb内存架构中有一块缓冲池,缓冲池避免了频繁与磁盘IO进行交互,数据增删改查时首先从缓冲池操作(缓冲池没有的话会从磁盘加载),缓冲池的数据被改了后,一段时间后被刷新到磁盘上。 从而加快处理速度。
缓冲池以页为单位,页有3种状态 空闲页,干净页—被使用过但是没有更改,脏页–使用过并且更改过
change buffer(更变缓冲区)
针对非唯一,二级缓冲区。 执行DML 更改数据时,如果数据不在buffer pool中,此时不会直接操作磁盘,而是将DML操作存在change buffer中。
自适应哈希索引
innodb是没有哈希索引的,但是不做范围查询的时候哈希索引很快,不冲突的情况下一次就可以找到。因此innodb有一个自动的哈希索引,无需手动干预,当观察到哈希索引可以提高速度时,会自动建立哈希索引
日志缓冲区
缓冲将要写入磁盘中的日志数据,默认大小16K,日志缓冲区跟缓冲池一样,定期刷新到磁盘中的。日志缓冲区的大小、刷新时间可以通过修改参数更改
innodb磁盘结构
双写缓冲区:内存空间的缓冲池数据刷新到磁盘前,会先写入到双写缓冲区,异常时可以用来回复数据。
redo log重做日志文件:实现事务的持久性。该日志文件有两部分,重做日志缓冲区在内存中,重做日志文件在磁盘中。 事务提交后会把所有修改信息存到重做日志文件中,重做日志文件用于恢复数据。
undo log撤销表空间: 对应有两个磁盘文件,用于撤销(事务没有提交之前都可以撤销)
临时表空间:存用户创建的临时表数据
4组后台线程(内存与磁盘之间的交互)
有4类线程。master thread管理线程 (调度其他线程、刷新脏页到磁盘)、IO thread处理AIO(异步非阻塞IO) 、purge thread(回收撤销日志文件,事务提交之后这块日志就不需要了)、 page clearner thread(协助管理线程刷新脏页到磁盘中)
master thread 类似线程池的管理线程。
事务的原理(4种特性怎么来的)
事务是一组操作的集合,是不可分割的工作单位。事务提交时所有操作一起提交,撤销时,所有操作一起撤销。
特性: ACID , 原子性、一致性、隔离性、持久性。
隔离级别回顾(读未提交、读已提交、可重复读、幻读)
redo log保证持久性(保证对数据的更改是永久的)、 undo log保证原子性(撤销跟提交是整个事务一起成功或者失败), redo log 与undo log保证 了一致性, MVCC+锁保证了隔离性
事务的隔离性基于锁和MVCC实现
redo log重做日志
redo log 保证了事务的持久性。
redo log 记录物理日志(记录在磁盘上的实际更改)。Innodb架构里面磁盘中就有redo log。 有两部分, redo log缓冲区在内存中,redo log 日志在磁盘中。事务提交的时候,所有对数据的操作都存在日志文件中,当刷新脏页到磁盘中发生错误时,redo log可以用来恢复。
当一组事务(update、delete之类的合集) 提交后,先在缓冲区写入重做日志文件缓冲,并且刷新到磁盘的重做日志文件。 一段时间后再刷新缓冲池被修改的数据到磁盘中。(不经过redolog 直接从缓冲池刷新到磁盘(这个刷新不是实时的,是一段时间过后恰当的时机,所以此时事务的提交已经成功了),如果一段时间后刷新到磁盘失败了,然而用户已经提交成功了,这就导致事务的持久性失效了)
为什么缓冲池的数据不实时刷新到磁盘:修改数据刷新到磁盘中,用到的磁盘IO是乱序的,性能很低。而重做日志文件是追加的,是顺序的,写入磁盘性能高
重做日志文件定期清除,因为提交成功了,这部分重做日志文件就没用了。
undo log回滚日志
用在事务提交、回滚之前。 保证了事务的原子性。作用是:提供回滚、MVCC(多版本并发控制)
undo log是逻辑日志(记录数据库操作的逻辑更改,只关心操作不关心物理存储),在insert、updata、delete的时候产生,便于数据回滚。
MVCC
当前读:读取的是数据的最新版本,读取时保证其他事务不能修改当前记录,会对读物记录加锁。如果想当前读,可以加锁实现
快照读:简单的select就是快照读,读的是数据的可见版本。RC隔离级别下,每次select都会生成一个快照读,在RR级别下,第一个select生成快照读,后续的读都是读这个快照。快照读根据MVCC查找数据版本返回。
MVCC实现原理
MVCC版本控制的作用:在快照读的时候,决定读的是哪个版本的数据。
MVCC原理 基于3个内容: 3个隐式字段、undo log、readview
3个隐式段在记录上, undo log跟3个隐式段联合形成版本链, readview在事务中生成。
记录中的 DB_TRX_ID跟 readview 的规则进行比对,返回能访问的数据版本。
记录中的3个隐式字段
每条数据记录的隐藏字段
记录会自动生成3个字段,DB_ROW_ID在没有主键的时候才生成。 DB_TRX_ID记录最后一次操作该数据的事务ID, DB_ROLL_Ptr指向上个版本的数据。
undo log版本链
操作数据时会生成版本链,链头部是最新的数据记录,尾部就是老的数据记录
修改记录时,在undo log中会保存修改记录,然后3个隐式段的回滚指针指向undo log修改日志,TRX_ID修改为对应的事务。 每次修改,PTR都指向undo log上个版本的信息,从而形成undo log 版本链
read view读视图
维护4个属性,根据4个属性决定当前快照读 到底读哪个版本的数据记录
undo log版本链的记录都是历史记录,一条查询语句到底读哪个历史记录根据readview来决定的。
readview是提取数据的依据(RC级别下的每次普通select,RR级别下的第一次select生成快照读)
RC级别下每次查询都生成readview RR级别下,第一次查询生成readview,后续会复用该readview
readview记录 4个核心字段,维护系统当前未提交的事务ID(活跃事务ID)
m_ids: 未提交的事务集合; min_trx_id:最小未提交事务; max_trx_id: 下次要提交的事务ID;
creator_trx_ID: readview创建者的事务ID(RC跟RR级别下,第一次select生成readview)
MCVV版本控制流程:
每条数据记录都有3个隐藏字段,隐藏字段联合undo log形成一条undo log版本链,一条SQL语句访问某条记录时,会拿隐藏字段的DB_TRB_ID字段也就是某条记录的最后一次修改事务ID 跟当前的readview规则进行比对,比对成功就可以访问隐藏字段对应的数据版本,比对不成功就根据数据记录的PTR回滚指针 找版本链记录的之前的记录,并且比对DB_TBX_ID跟readview。
提取规则
trx_id是数据记录中的DB_TRX_ID。
总之: RC隔离级别下,可以访问 已经提交的记录, 当前事务操作过的记录
在RR级别下,规则是一样的,不一样的是readview不会新生成。(这就是可重复读的原理,因为readview一样,能读的数据版本也是一样的)
总结
系统数据库/常用工具
自带4个系统数据库
mysqladmin:不登陆mysql情况下管理mysql
进阶篇总结
存储引擎:innodb(事务、外键、行级锁)
索引
SQL优化:索引使用原则,SQL语句优化(围绕索引)
视图、触发器
锁:全局锁、表级锁、行级锁
Innodb的核心