数据库相关概念:
数据库(DB-database):存储数据的仓库,数据是有组织的进行存储
数据库管理系统(DBMS-database Management system):操纵和管理数据库的大型软件
SQL(structured query language):操作关系型数据库的编程语言,定义了一套操作数据库的统一标准
mysql启动:(默认开机自启,mysql是sql设置的系统服务名称)
net start mysql
mysql停止:
net stop mysql
客户端连接:cmd:
mysql[-h 127.0.0.1] [-p 3306] -u -root -p
想要在任意目录下运行mysql需要把mysql目录下的bin文件目录添加到环境变量中
mysql数据模型:
Mysql体系结构:
连接层
最上层是一些客户端和链接服务,主要完成一些类似于连接处理、授权认证、及相关的安全方案。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
服务层
第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如 过程、函数等。
引擎层
存储引擎真正的负责了MVSOL中数据的存储和提取,服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。
存储层
主要是将数据存储在文件系统之上,并完成与存储引擎的交互。
SQL通用语法
1.SQL语句可以单行或多行书写,以分号结尾。
2.SQL语句可以使用空格/缩进来增强语句的可读性,
3.MySQL数据库的SQL语句不区分大小写,关键字建议使用大写。
4.注释:
单行注释: --注释内容 或 # 注释内容(MySQL特有)
多行注释: /*注释内容 */
SQL分类:
DDL数据定义(definition)语言:定义数据库对象
DML数据操纵(Manipulation)语言:对库中数据进行增删改查
DQL数据查询(query)语言:查询库中记录
DCL数据控制(control)语言:创建数据库用户,控制数据库访问权限
DDL-数据库操作
查询所有数据库:
show databases;
查询当前数据库:
select databases();
创建:
create database if not exists database_name [default charset 字符集] [collate 排序规则];
删除:
drop database if exists database_name;
使用数据库:
use database_name;
DDL-表操作
查询所有表:
show tables;
查询表结构:
desc table_name;
查询建表语句:
show create table table_name;
创建表:
create table table_name(
var1 type1[comment var1注释],
var2 type2[comment var2注释],
)[comment 表注释];
|------|-----------|----|----------------------|----------------|-------|
| 分类 | 类型 | 大小 | 有符号范围 unsigned | 无符号范围 unsigned | 描述 |
| 数值类型 | Tinyint | 1B | (-128,127) | (0,255) | 小整数值 |
| 数值类型 | smallint | 2B | (-32768,32767) | (0,65535) | 大整数值 |
| 数值类型 | mediumint | 3B | (-8388608,8388607) | (0,16777215) | 大整数值 |
| 数值类型 | int | 4B | (-2^10,2^10-1) | (0,4.29^10) | 大整数值 |
| 数值类型 | bigint | 8B | (-2^63,2^63-1) | (0,2^64-1) | 极大整数值 |
| 数值类型 | float | 4B | (-3.4^38,3.4^38-1) | (0,3.4^38) | 单精度 |
| 数值类型 | double | 8B | | | 双精度 |
| 数值类型 | decimal | | 依赖精度M和标度D的值 || 小数值 |
| 数值类型 | Age tinyint unsigned ; score double(4,1) |||||
|-------|------------|-----------------------|-----------------|
| 字符串类型 | 类型 | 大小 | 描述 |
| 字符串类型 | CHAR | 0-255 bytes | 定长字符串 |
| 字符串类型 | VARCHAR | 0-65535 bytes | 变长字符串 |
| 字符串类型 | TINYBLOB | 0-255 bytes | 不超过255个字符的二进制数据 |
| 字符串类型 | TINYTEXT | 0-255 bytes | 短文本字符串 |
| 字符串类型 | BLOB | 0-65 535 bytes | 二进制形式的长文本数据 |
| 字符串类型 | TEXT | 0-65 535 bytes | 长文本数据 |
| 字符串类型 | MEDIUMBLOB | 0-16 777 215 bytes | 二进制形式的中等长度文本数据 |
| 字符串类型 | MEDIUMTEXT | 0-16 777 215 bytes | 中等长度文本数据 |
| 字符串类型 | LONGBLOB | 0-4 294 967 295 bytes | 二进制形式的极大文本数据 |
| 字符串类型 | LONGTEXT | 0-4294 967 295 bytes | 极大文本数据 |
| 字符串类型 | Char(10) 性能好(名称);varchar(10) 性能差(性别) |||
|------|-----------|----|-----------------------------------------|---------------------|------|
| 分类 | 类型 | 大小 | 范围 | 格式 | 描述 |
| 日期格式 | date | 3 | 1000-01-01至9999-12-31 | YYYY-MM-DD | 日期值 |
| 日期格式 | time | 3 | -838.59.59至838.59.59 | HH:MM:SS | 时间值 |
| 日期格式 | year | 1 | 1901至2155 | YYYY | 年份值 |
| 日期格式 | datetime | 8 | 1000-01-01 00:00:00至9999-12-31:59:59:59 | YYYY-MM-DD HH:MM:SS | 具体时间 |
| 日期格式 | timestamp | 4 | 1970-01-01 00:00:01至2038-12-31:59:59:59 | YYYY-MM-DD HH:MM:SS | 时间戳 |
设计一张员工信息表,要求如下:
1.编号(纯数字)
2.员工工号(字符串类型,长度不超过10位)
3.员工姓名(字符串类型,长度不超过10位
4.性别(男/女,存储一个汉字)
5.年龄(正常人年龄,不可能存储负数)
6.身份证号(二代身份证号均为18位)
7.入职时间(取值年月日即可)
create table emp(id int comment '编号',
workno varchar(10) comment '工号',
name varchar(10) comment '姓名',
gender char(1) comment'性别',
age tinyint unsigned comment'年龄',
idcard char(18) comment'身份证号',
entrydate date comment '入职时间'
)comment '员工表';
DDL-表操作-修改
添加字段:
Alter table table_name add 字段名 类型(长度)[comment 注释] [约束];
修改数据类型:
Alter table table_name modify 字段名 新数据类型(长度);
修改字段名和字段类型:
Alter table table_name change 旧字段名 新字段名 类型(长度)[comment 注释] [约束];
删除字段:
Alter table table_name drop 字段名;
修改表名:
Alter table table_name rename to new_table_name;
DDL-表操作-删除
删除表:
Drop table if exist table_name;
删除指定表并重新创建该表:
Truncate table table_name;
DML
添加数据:
Insert into table_name(字段名1,字段名2,...) values(value1,value2,...);
Insert into table_name values(value1,value2,...);
批量添加:
Insert into table_name(字段名1,字段名2,...) values(value1,value2,...),(value1,value2,...),...;
Insert into table_name values(value1,value2,...),(value1,value2,...),...;
插入数据时,指定的字段顺序要和值的顺序一一对应;字符串和日期型数据应该包含在引号中;插入的数据大小应该在字段规定的范围之内。
修改数据:
Update table_name set 字段名1=nalue1,字段名2=value2,...[where 条件];
--没有where时修改整张表的数据
删除数据:
Delete from table_name[where 条件];
--没有where时删除整张表的数据,delete语句不能删除某一个字段的值(可以用update)
DQL
DQL 执行顺序
Selete 字段列表 4
From 表名列表 1基本查询
Where 条件列表 2条件查询(where)
Group by 分组字段列表 聚合函数(count,max,min,avg,sum)
Having 分组后条件列表 3分组查询(group by)
Order by 排序字段列表 5排序查询(order by)
Limit 分页参数 6分页查询(limit)
Select * from table_name;
Select 字段1,字段2,... from table_name;
设置别名:
Select 字段1 [ as] 别名1,字段2 [ as] 别名2, ... from table_name;
去除重复记录:
Select distint 字段列表 from table_name;
条件查询:
Select 字段1,字段2,... from table_name where 条件列表;
条件:> ≥ < ≤ = <>或!= between...and... in(...) like '%' is null
And或&& or或|| not 或! [其中%匹配任意个字符,_匹配一个字符]
聚合函数(将一列数据作为一个整体进行纵向计算):
Select 聚合函数(字段列表) from table_name;
Null不参与所有聚合函数的运算。
Eg:查询年龄小于45,根据工作地址分组,获取员工数量大于等于3的工作地址
Select addr,count(*) from emp where age<45 group by addr havingcount(*)>3;
分组查询:
Select 字段列表 from table_name [where 条件] group by 分组字段名 [having 分组后过滤条件];
Where和having区别:
执行时机不同:where在分组之前过滤,不满足where条件不参与分组,having在分组后对结果过滤
判断条件不同:where不能对聚合函数进行判断,而having可以。
执行顺序:where>聚合函数>having
分组后,查询的字段一般为聚合函数和分组字段,查询其他字段没有意义。
排序查询:
Select 字段列表 from table_name order by 字段1 排序方式1,字段2 排序方式2;
Asc:升序(default),desc:降序,如果多字段排序,当第一个字段值相同时,才会根据第二个字段进行排序。
分页查询:
Select 字段列表 from table_name limit 起始索引(start_index),查询记录数;
start_index从0开始,start_index=(查询页数-1)*每页显示记录数
分页查询是mysql的方言,不同sql有不同实现方式,mysql是limit;
如果查询的是第一页数据,起始索引可以省略,直接简写为limit num;
select * from emp where gender = 'man' and age between 20 and 40 order by age asc , entrydate asc limit 5;
DCL
查询用户:
Use mysql;
Select * from user;
创建用户:
Create user '用户名'@'主机名' indentified by 'password';
修改用户密码:
Alter user '用户名'@'主机名' indentified with mysql_native_password by 'new_password';
删除用户:
Drop user '用户名'@'主机名';
主机名可以用%通配;
权限控制:
ALL,ALLPRIVILEGES 所有权限
Select 查询数据
Insert 插入数据
Update 修改数据
Delete 删除数据
Alter 修改表
Drop 删除表/库/视图
Create 创建表/库/视图
查询权限:
Show grants for '用户名'@'%';
授予权限:
Grant 权限列表[all] on 数据库名.表名 to '用户名'@'%';
撤销权限:
Revoke 权限列表[all] on 数据库名.表名 to '用户名'@'%';
多个权限之间用逗号分隔
授权时数据库名和表名可以用*通配;
函数
一段可以直接被另一端程序调用的程序或代码。
select function(参数);
|---------------------------------------------------------------|------------------------------------|
| 字符串函数 | |
| Concat(s1,s2,...sn) | 拼接,将s1,s2,...拼接成一个字符串 |
| Lower(str) | 将字符串str全转换成小写 |
| Upper(str) | 将字符串str全转换成大写 |
| Lpad(str,n,pad) | 左填充,用pad对str的左边填充,达到n长度 |
| Rpad(str,n,pad) | 右填充,用pad对str的右边填充,达到n长度 |
| Trim(str) | 去掉字符串头部和尾部空格 |
| Substring(str,start,len) | 返回字符串str从start 位置开始的len长度的字符串 |
| 数值函数 | |
| Cell(x) | 向上取整 |
| Floor(x) | 向下取整 |
| Mod(x,y) | 返回x/y的模 |
| Rand() | 返回0到1内的随机数 |
| Round(x,y) | 求参数x的四舍五入的x值,保留y位小数 |
| 日期函数 | |
| Curdate() | 当前日期 |
| Curtime() | 当前时间 |
| Now() | 当前日期和时间 |
| Year(date) | 获取日期的年份 |
| Month(date) | 获取日期的月份 |
| Day(date) | 获取日期的日 |
| Date_add(date,interval exp type) | 日期加上一个时间间隔exp后的时间值 |
| Datediff(date1,date2) | 返回起始时间date1 - 结束时间date2之间天数 |
| 流程函数(实现条件筛选) | |
| If(value,t,f) | |
| Ifnull(value1,value2) | |
| Case when [var1] then [res1] ... else [default] end | 如果var1(条件)为true,返回res1,否则返回default |
| Case exp when [var1] then [res1] ... else [default] end | 如果exp的值等于var1,返回res1,否则返回default |
| 生成一个六位数的随机验证码:lpad(round(rand()*1000000, 0), 6, '0') ||
| Select name, (case workaddress when '北京' then '一线城市' else '二线城市' end ) as 'address' from emp; ||
| Select id, name, (case when score>=90 then '优秀' else '良好' end) as '等级' from student; ||
约束
作用于表上的规则,限制存储在表中的数据,保证库中数据的准确性,有效性和完整性。
约束是作用在表中字段上的,可以在创建表/修改表的时候添加约束。
分为
|------|-----------------------|-------------|
| 约束 | 描述 | 关键字 |
| 非空约束 | 该字段数据不能为null | Not null |
| 唯一约束 | 保证该字段所有数据唯一,不重复 | unique |
| 主键约束 | 主键是一行数据的唯一标识,要求非空且唯一 | Primary key |
| 默认约束 | 若未指定该字段值,采用默认值 | default |
| 检查约束 | 保证字段值满足某一个条件 | check |
| 外键约束 | 让两张表建立连接,保证数据的一致性和完整性 | Foreign key |
主键可以设置成自动增长(mysql):auto_increment; oracle: sequence
CREATE TABLE tb_user(
id int AUTO_INCREMENT PRIMARY KEY COMMENT 'ID唯一标识',
name varchar(10) NOT NULL UNIQUE COMMENT '姓名',
age int check (age>0&& age<=120) COMMENT '年龄',
status char(1) default '1' COMMENT '状态',
gender char(1) COMMENT '性别'
);
子表中有父表的id列,没有外键约束的两张表虽然逻辑上有关联,但若删除父表中数据,子表中数据不会该改变,此时无法保证数据完整性和一致性。
若添加了外键约束则父表中数据不能随意被删除(被关联了),确保数据的完整性。
添加外键:
Create table table_name(字段名 type, ... [constraint] [外键名称] foreign key(外键字段名) references 主表(主表列名));
Alter table table_name add constraint 外键名称 foreign key(外键字段名) references 主表(主表列名);
Alter table emp add constraint emp_dept_id foreign key(dept_id) references dept(id);
删除外键:
Alter table table_name drop foreign key 外键名称;
外键约束删除更新行为:
Alter table table_name add constraint 外键名称 foreign key(外键字段名) references 主表(主表列名) on update cascade on delete cascade;
|-------------|---------------------------------------------------------------|
| 行为 | 说明 |
| NO ACTION | 当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有则不允许删除/更新。(与RESTRICT一致) |
| RESTRICT | 当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有则不允许删除/更新。(与 NOACTION一致) |
| CASCADE | 当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有,则也删除/更新外键在子表中的记录。 |
| SET NULL | 当在父表中删除对应记录时,首先检查该记录是否有对应外键,如果有则设置子表中该外键值为nul(这就要求该外键允许取nul)。 |
| SET DEFAULT | 父表有变更时,子表将外键列设置成一个默认的值(Innodb不支持) |
多表关系
设计表结构时各个表之间会存在相互关联关系,分为三种:
一对多(多对一):多的一方建立外键,指向一的一方主键
多对多:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键
一对一:任意一方加入外键,关联另外一方的主键,并设置外键为唯一的(unique)
多表查询:从多张表中查询数据,需要消除无效的笛卡尔积(集合A和集合B的所有可能组合)分为:
连接查询:
内连接:查询两张表的交集部分
Select 字段列表 from table1,table2 where 条件...;
Select 字段列表 from table1 inner join table2 on 连接条件...;
外连接:
Left join:查询左表所有数据,以及两表交集部分
Select 字段列表 from table1 left join table2 on 连接条件...;
Right join:查询右表所有数据,以及两表交集部分
Select 字段列表 from table1 right join table2 on 连接条件...;
自连接:表与自身的连接查询,必须使用别名(可以是内连接也可以是外连接)
Select 字段列表 from table1 name1 join table2 name2 on 条件...;
select a.name员工',b.name '领导' from emp a left join emp b on a.manager_id = b.id;
联合查询:union union all ( 把多次查询的结果合并起来,形成一个新的查询结果集)
Select 字段列表 from table1 union [all] select 字段列表 from table2 ...;
前提是联合查询的多张表的列数必须保持一致,字段类型也需要保持一致
Union all会将所有数据直接合并在一起, union会对合并后的数据去重。
子查询:sql语句中嵌套select语句,嵌套查询,子查询的外部查询可以是insert/update/delete/select任何一个。
Select * from table1 where column1 = (select column1);
根据子查询位置又分为: where之后、 from 之后、select之后
根据查询结果不同分为:
标量子查询(查询结果为单个值)
操作符: = <> > >= < <=
列子查询(查询结果为一列(多行))
|--------|--------------------------|
| 操作符 | 描述 |
| in | 在指定集合范围之内,多选一 |
| Not in | 不在制定集合范围之内 |
| any | 子查询返回列表中,有任意一个满足即可 |
| some | 与any等同,使用some的地方都可以使用any |
| All() | 子查询返回列表的所有值都必须满足 |
比任意一人工资高:
select *from emp where salary > some(select salary from emp where dept_id=(select id from dept where name ='研发部'));
行子查询(查询结果为一行)
操作符:= <> in not in
表子查询(查询结果为多行多列) in
Select e.*, d.* from (select * from emp where entrydate > '2020-10-10') e left join dept d on d.id=e.dept_id;
事务
:一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
查看事务:
Select @@autocommit;
Set @@autocomit=0;
开启事务:
Start transaction 或begin;
提交事务:
Commit;
回滚事务:
Rollback;
事务四大特性:
原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
隔离性(lsolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
并发事务问题:
脏读:
一个事务读到另外一个事务还没提交的数据。
不可重复读:
同一个事务先后读取同一条记录,但两次读取记录的数据不同。
幻读:一个事务按照条件查询数据时,没有对应数据行,但在插入数据时,又发现这行数据存在,彷佛出现了幻影。
事务隔离级别:
查看事务隔离级别:
Select @@transaction_isolation;
设置事务隔离级别:
Set [session/global] transaction isolation level {read uncommited/read commited/repeatable read/serializable}
事务隔离级别越高,数据越安全,但是性能越低,serializable只允许同时进行一个事务,隔离级别最高
|-----------------|----|-------|----|
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
| read uncommited | √ | √ | √ |
| read commited | × | √ | √ |
| repeatable read | × | × | √ |
| serializable | × | × | × |
存储引擎
存储数据、建立索引、更新/查询数据等技术的实现方式,存储引擎是基于表的,不是基于库的,所以存储引擎可以被成为表类型。
创建表时,指定存储引擎:
Create table table_name(
字段 1 type [cemment 字段1注释],
字段2 type [cemment 字段2注释],...
)engine = innodb[cemment 表注释];
查看数据库支持的存储引擎:
Show engines;
存储引擎特点
Innodb:一种兼顾高性能和高可靠性的通用存储引擎,
DML遵循ACID模型,支持事务;
具备行级锁,提高并发访问性能;
支持外键foreign key 约束,保证数据完整性和准确性。
文件:
xxx.ibd: xxx代表的是表名,innoDB引擎的每张表都会对应这样一个表空间文件.
用来存储该表的表结构(frm、sdi)、数据和索引。
参数: innodb_file_per_table
Cmd: ibd2sdi account.ibd
存储逻辑结构:
表空间:(ibd文件),一个mysql实例可以对应多个表空间,用于存储记录、索引等数据。
段:分为数据段(Leafnodesegment)、索引段(Non-leafnodesegment)、回滚段(Rollbacksegment),InnoDB是索引组织表,数据段就是B+树的叶子节点,索引段即为B+树的非叶子节点。段用来管理多个Extent(区)。
区:表空间的单元结构,每个区的大小为1M。默认情况下,InnoDB存储引擎页大小为16K,即一个区中一共有64个连续的页。
页:是InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默认为16KB。为了保证页的连续性,InnoD8 存储引擎每次从磁盘申请 4-5 个区。
行:InnoDB 存储引擎数据是按行进行存放的。
Trx_id:每次对某条记录进行改动时,都会把对应的事务id赋值给trx_id隐藏列。
Roll_pointer:每次对某条引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它找到该记录修改前的信息。
MyISAM:
是早期mysql默认存储引擎
不支持事务,不支持外键
支持表级锁,不支持行锁
访问速度快
对应文件:
Xxx.sdi存储表结构信息
Xxx.MYD 存储数据
Xxx.MYI 索引存储
Memory:
该引擎的表数据是存储在内存中,由于受到硬件问题或断电问题影响,只能将这些表数据作为临时表或缓存使用。
其特点是:
内存存放
hash索引
文件:
Xxx.sdi 存储表结构信息
|-----------------|--------|--------|--------|
| 特点 | InnoDB | MyISAM | Memory |
| 存储限制 | 64TM | 有 | 有 |
| 事务安全 | 支持 | - | - |
| 锁机制 | 行锁 | 表锁 | 表锁 |
| B+tree索引 | 支持 | 支持 | 支持 |
| hash索引 | - | - | 支持 |
| 全文索引 | 支持 | 支持 | - |
| 空间使用 | 高 | 低 | NA |
| 内存使用 | 高 | 低 | 中等 |
| 批量插入速度 | 低 | 高 | 高 |
| 支持外键 | 支持 | - | - |
| 支持索引 | | | |
| B+tree索引 | 支持 | 支持 | 支持 |
| hash索引 | 不支持 | 不支持 | 支持 |
| R-tree索引(空间索引) | 不支持 | 支持 | 不支持 |
| Full-text(全文索引) | 支持 | 支持 | 不支持 |
存储引擎选择
在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。
InnoDB:是Msql的默认存储引擎,支持事务、外键。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操作,那么InnoDB存储引擎是比较合适的选择。
MyISAM :如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。
MEMORY:将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。MEMORY的缺陷就是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性。
索引
索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。
二叉树、红黑树、B-tree、B+tree等等
无索引时可能进行的是全表扫描,效率低,有索引会对字段进行索引排序比较,速度快。
Select * from where age=5;
|-----------------------------|-------------------------------------------------|
| 优势 | 劣势 |
| 提高数据检索效率,降低数据库的IO成本 | 索引列也需要占用空间 |
| 通过索引列对数据进行排序,降低排序成本,降低cpu消耗 | 提高查询效率,但也降低了表的更新速度,对表的insert、update、delete时效率降低 |
Mysql索引是在存储引擎层实现,不同存储引擎有不同存储结构,主要包含:
|-----------------|----------------------------------------|
| 索引结构 | 描述 |
| B+tree索引 | 最常见的索引,大部分引擎都支持B+tree索引 |
| hash索引 | 底层数据结构用hash表实现,只有精确匹配索引列的查询才有效,不支持范围查询 |
| R-tree索引(空间索引) | 是MyISAM引擎的一个特殊索引,用于地理空间数据类型,通常使用较少 |
| Full-text(全文索引) | 建立倒排索引快速匹配文档的方式,类似于Solr、ES |
二叉树索引:
缺点:顺序插入时会形成一个链表,查询性能大大降低,大数据层级下,层次较深,检索速度慢。
红黑树:大数据层级下,层次深,检索速度慢。
B-Tree(多路平衡查找树)
以一颗最大度数(max-degree)为5(5阶)的b-tree为例(每个节点最多存储4个key,5个指针):
树的度数指的是一个节点的子节点个数(即指针个数):
树的动态变化参考:Data Structure Visualization (usfca.edu)
B+Tree
以一颗最大度数(max-degree)为4(4阶)的b+tree为例(每个节点最多存储3个key,4个指针):
非叶子节点中数据起到索引作用;所有叶子节点数据都会出现在叶子节点中;叶子节点形成一个单向链表。
Mysql索引对B+tree进行了优化,增加了一个指向相邻叶子节点的链表指针,形成带有顺序指针的B+tree,提高区间访问的性能。
Hash索引:
采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中;如果两个(或多个)键值,映射到一个相同的槽位上,他们就产生了hash冲突(也称为hash碰撞),可以通过链表来解决。
1.Hash索引只能用于对等比较(=,in),不支持范围查询(between,>,<,..)
2.无法利用索引完成排序操作
3.查询效率高,通常只需要一次检索就可以了,效率通常要高于B+tree索引
存储引擎支持
在MySQL中,支持hash索引的是Memory引擎,而innoDB中具有自适应hash功能,hash索引是存储引擎根据B+Tree索引在指定条件下自动构建的。
为什么InnoDB存储引擎选择使用B+tree索引结构?
相对于二叉树,层级更少,搜索效率高;
对于B-tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低,
对于hash索引,B+tree支持范围匹配和排序操作
索引分类:
|------|-------------------|-------------|----------|
| 分类 | 含义 | 特点 | 关键字 |
| 主键索引 | 针对表中主键创建的索引 | 默认自动创建,只有一个 | PRIMARY |
| 唯一索引 | 避免同一个表中某数据列的值重复 | 可以有多个 | UNIQUE |
| 常规索引 | 快速定位特定数据 | 可以有多个 | |
| 全文索引 | 查找全文关键字,而非比较索引中的值 | 可以有多个 | FULLTEXT |
在InnoDB存储引擎中,根据索引的存储形式,又可以分为以下两种:
|------------------------|-------------------------------|------------|
| 分类 | 含义 | 特点 |
| 聚集索引 (Clustered Index) | 将数据存储与索引放到一块,索引结构的叶子节点保存了行数据 | 必须有,而且只有一个 |
| 二级索引 (Secondary Index) | 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 | 可以存在多个 |
聚集索引选取规则:
如果存在主键,主键索引就是聚集索引。
如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。
如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引。
Select * from user where name='arm'; [回表查询]
InnoDB主键索引的B+tree高度为多高?
假设:一行数据大小为1k,一页中可以存储16行这样的数据。InnoDB的指针占用6个字节的空间,主键即使为bigint,占用字节数为8,一个区内可以存放n个数据:
n*8+(n+1)*6=16*1024,算出n约为1170
高度为2可以存储的数据量为:
1171*16=18736
高度为3可以存储的数据量为:
1171*1171*16=21939856
创建索引
CREATE [ UNIQUE | FULLTEXT] INDEX index_name ON table_name ( index_col_name...) ;
查看索引
SHOW INDEX FROM table_name ;
删除索引
DROP INDEX index_name ON table_name ;
Eg:为email创建合适的索引提升查询效率:
提升效率可以用substring截断email长度创建索引
SQL性能分析
SQL执行频率
MySQL客户端连接成功后,通过show [session/global] status命令可以提供服务器状态信息。通过如下指令,可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次:
SHOW GLOBAL STATUS LIKE 'Com______';-- (6个下划线)
慢查询日志
慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志。MySQL的慢查询日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf)中配置:
-- 开启MySQL慢日志查询开关
slow_query_log=1
-- 设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2
-- 配置完毕之后,通过以下指令重新启动MySQL服务器进行测试,查看慢日志文件中记录的信息
/var/lib/mysql/localhost-slow.log。
profile详情
show profiles 能够在做SQL优化时帮助我们了解时间都耗费在哪里。通过have_profiling参数,能够看到当前MySQL是否支持profile操作:
SELECT @@have__profiling ;
默认profiling是关闭的,可以通过set语句在session/global级别开启profiling:
SET profiling = 1;
执行一系列的业务SQL的操作,然后通过如下指令查看指令的执行耗时:
-- 查看每一条SQL的耗时基本情况
show profiles;
-- 查看指定query_id的SQL语句各个阶段的耗时情况
show profile for query query_id;
-- 查看指定query_id的SQL语句CPU的使用情况
show profile cpu for query query_id;
explain执行计划
EXPLAIN 或者 DESC命令获取 MySQL如何执行 SELECT语句的信息,包括在 SELECT 语句执行过程中表如何连接和连接的顺序。语法:
-- 直接在select语句之前加上关键字 explain/desc
EXPLAIN SELECT 字段列表 FROM 表名WHERE 条件;
EXPLAIN 执行计划各字段含义:
id
select查询的序列号,表示查询中执行select子句或者是操作表的顺序(id相同,执行顺序从上到下; id不同,值越大,越先执行)。
Select_type
表示 SELECT 的类型,常见的取值有 SIMPLE(简单表,即不使用表连接或者子查询)、PRIMARY(主查询,即外层的查询(有时包含嵌套子查询))、UNION(UNION 中的第二个或者后面的查询语句)、SUBQUERY(SELECT/WHERE之后包含了子查询)等
Type(唯一主键const;非唯一主键ref)
表示连接类型,性能由好到差的连接类型为NULL、system、const、eq_ref、ref、range、index、all 。
Possible_key
显示可能应用在这张表上的索引,一个或多个
Key
实际使用的索引,如果为NULL,则没有使用索引。
Key_len
表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好
rows
MySQL认为必须要执行查询的行数,在innodb引擎的表中,是一个估计值,可能并不总是准确的。
filtered
表示返回结果的行数占需读取行数的百分比,filtered的值越大越好。
Extract
为NULL意味着需要回表查询
索引使用:
验证索引效率
在未建立索引之前,执行如下SQL语句,查看SQL的耗时。
SELECT * FROM tb_sku WHERE sn=1;
针对字段创建索引
create index idx_sku_sn on tb_sku(sn);
然后再次执行相同的SQL语句,再次查看SQL的耗时。
SELECT * FROM tb_sku WHERE sn=1;
最左前缀法则
如果索引了多列(联合索引),要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。
如果跳跃某一列,索引将部分失效(后面的字段索引失效)。
联合索引中查询时必须存在索引中的最左侧列,否则该索引失效,就是进行的全表扫描。
查询时只要有对应列即可,跟列放的位置无关。
创建索引时列的位置顺序有影响。
explain select * from tb_user where profession ='软件工程' and age = 1 and status = '0';
explain select * from tb_user where profession='软件工程" and age = 1;
explain select * from tb user where age = 31 and status = '0';
范围查询:
联合索引中,出现范围查询(>;<),范围查询右侧的列索引都失效
explain select * from tb_user where profession ='软件工程' and age > 1 and status = '0';
索引列运算
不要再索引列上进行运算操作,索引列将失效
explain select * from tb_user where substring(profession,1,2)='软件';
字符串不加引号
字符串类型字段使用时,不加引号,索引将失效。
explain select * from tb_user where profession = "软件工程' and age = 31 and status = 0;
explain select * from tb_user where phone = 17799990015;
模糊查询
如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。
explain select * from tb_user where profession like "软件%'; --正常
explain select * from tb user where profession like '%工%; --失效
or连接的条件
用or分割开的条件,如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。
explain select * from tb_user where id = 10 or age = 23;
explain select * from tb_user where phone ='17799990017' or age = 23;
由于age没有索引,所以即使id、phone有索引,索引也会失效。所以age也要建立索引。
数据分布影响:
如果mysql评估使用索引比全表查询更慢,则不适用索引 --(假设共有20名学生)
explain select * from tb_user where phone >='17799990003'; --不会用索引
explain select * from tb_user where phone >='17799990017'; --会用索引
SQL提示:
是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。
use index:
explain select * from tb_user use index(idx_user_pro) where profession ='软件工程'; --建议使用,不一定使用
ignore index:
explain select * from tb_user ignore index(idx_user_pro) where profession ='软件工程'; --不要使用
force index:
explain select * from tb_user force index(idx_user_pro) where profession ='软件工程'; --必须使用
覆盖索引
尽量使用覆盖索引,减少select *的使用,减少回表查询,提升查询效率。(查询使用索引并且需要返回的列能够在该索引中全部找到)
前缀索引
当字段类型为字符串(varchar, text等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时浪费大量的磁盘IO,影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。
create index idx_xx on table name(column(n));
前缀长度
可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
select count(distinct email) /count(*) from tb_user ;
select count(distinct substring(email,1,5)) / count(*)from tb_user ;
单列索引与联合索引
单列索引:即一个索引只包含单个列。
联合索引:即一个索引包含了多个列。
在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引。
索引设计原则
索引主要针对select优化的,insert很少。
针对于数据量较大,且查询比较频繁的表建立索引。
针对于常作为查询条件(where)、排序(orderby)、分组(groupby)操作的字段建立索引。
尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。
要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。
如果索引列不能存储NULL值,请在创建表时使用NOT NULL进行约束。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询。
SQL优化
插入数据-insert优化
批量插入
Insert into tb_test values(1,'Tom'),(2,'Cat'),(3, Jerry');
手动提交事务
start transaction;
insert into tb_test values(1,'Tom'),(2,'Cat'),(3, 'jerry');
insert into tb test values(4,'Tom'),(5,'Cat'),(6,' erry');
insert into tb test values(7,'Tom'),(8,'Cat'),(9, 'jerry');
commit;
主键顺序插入(顺序插入性能高于乱序插入)
主键乱序插入:8 19 2 18
主键顺序插入:2 8 18 19
大批量插入数据
如果一次性需要插入大批量数据,使用insert语句插入性能较低,此时可以使用MySQL数据库提供的load指令进行插入。操作如下:
-- 客户端连接服务端时,加上参数 --local-infile
Mysql --local-infile -u root -p
-- 设置全局参数local_infile为1,开启从本地加载文件导入数据的开关
set global local_infile =1;
-- 执行load指令将准备好的数据,加载到表结构中
load data local infile '/root/sql1.log' into table tb_user ' fields terminated by '.' lines terminated by '\n' ;
主键优化
数据组织方式
在InnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(index organized table IOT)。
页分裂
页可以为空,也可以填充一半,也可以填充100%。每个页包含了2~N行数据(如果一行数据过大,会行溢出),根据主键排列。
页合并
当删除一行记录时,实际上记录并没有被物理删除,只是记录被标记(flaged)为删除并且它的空间变得允许被其他记录声明使用。当页中删除的记录达到 MERGE THRESHOLD(默认为页的50%),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间使用。(MERGE THRESHOLD:合并页的阈值,可以自己设置,在创建表或者创建索引时指定)
主键设计原则
满足业务需求的情况下,尽量降低主键的长度。
插入数据时,尽量选择顺序插入,选择使用AUTOINCREMENT自增主键
尽量不要使用UUID做主键或者是其他自然主键,如身份证号
业务操作时,避免对主键的修改
Order_by优化
①Using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort bufer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。
②Using index:通过有序索引顺序扫描直接返回有序数据,这种情况即为using index,不需要额外排序,操作效率高。
Filesort(性能下降)
-- 没有创建索引时,根据age,phone进行排序
explain select id,age,phone from tb_user order by age , phone;
ad on tb_user(age asc, phone desc)
-- 创建索引
create index idx_user_age_phone_aa on tb_user(age, phone);
create index idx_user_age_phone_ad on tb_user(age asc, phone desc);
index
-- 创建索引后,根据age,phone进行升序排序
explain select id,age,phone from tb_user order by age , phone;
-- 创建索引后,根据age,phone进行降序排序
explain select id,age,phone from tb_user order by age desc , phone desc ;
根据排序字段建立合适的索引,多字段排序时也遵循最左前缀法则。
尽量不要使用selct * , 尽量使用覆盖索引。
多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)。
如果不可避免的出现filesort,大数据量排序时,可以适当增大排序缓冲区大小sort_buffer_size(默认256K)。
group by优化
-- 删除目前的联合索引idx_user_pro_age_sta
drop index idx_user_pro_age_sta on tb _user;
-- 执行分组操作,根据profession字段分组
explain select profession , count(*) from tb_user group by profession;
-- 创建索引
Create index idx_user_pro_age_sta on tb_user(profession , age , status);
-- 执行分组操作,根据profession字段分组
explain select profession , count(*) from tb_user group by profession;
-- 执行分组操作,根据profession字段分组
explain select profession , count(*) from tb_user group by profession, age,
在分组操作时,可以通过索引来提高效率。
分组操作时,索引的使用也是满足最左前缀法则的。
where profession='软件工程' group by age也遵循index
Limit优化
常见又非常头疼的问题就是 limit 2000000,10,此时需要MySQL排序前2000010记录,仅仅返回2000000-2000010的记录,其他记录丢弃,查询排序的代价非常大。
优化思路: 一般分页查询时,通过创建 覆盖索引 能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化。(多表联合查询,不要查 *)
explain select * from tb_sku t , (select id from tb_sku order by id limit 2000000,10) a where t.id = a.id;
count优化
explain select count(*) from tb_user;
MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行count(*)的时候会直接返回这个数,效率很高;
InnoDB 引擎执行count(*)的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。
优化思路:自己计数。
count的几种用法
count()是一个聚合函数,对于返回的结果集,一行行地判断,如果count 函数的参数不是 NULL,累计值就加1,否则不加,最后返回累计值。
用法:count(*)、count(主键)、count(字段)、count(1)
性能比较:count(*)=count(1)>count(主键)>count(字段)
count(主键)
InnoDB 引擎会遍历整张表,把每一行的主键id值都取出来,返回给服务层。服务层拿到主键后,直接按行进行累加(主键不可能为nul)。
count(字段)
没有not null约束:InnoD8 引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null,计数累加。
有not null 约束:InnoDB 引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,直接按行进行累加。
count(1)
InnoD8 引擎遍历整张表,但不取值。服务层对于返回的每一行,放一个数字"1"进去,直接按行进行累加。
count(*)
InnoDB引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加。
update优化
update student set no='2000100100' where id =1; -- 行锁(主键查询)
update student set no='2000100105 'where name ='job'; -- 表锁(回表查询)
InnoDB的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则会从行锁升级为表锁。
修改值时尽量将条件对索引或主键进行修改,否则会将行级锁升级为表锁影响并发性能。
视图
就是一张虚拟存在的表
创建
CREATE [OR REPLACE] VIEW 视图名称(列名列表) AS SELECT语句[WITH[CASCADED|LOCAL] CHECK OPTION]
查询
SHOW CREATE VIEW 视图名称;
查看视图数据:
SELECT * FROM 视图名称 ...;
修改
方式一:
CREATE [OR REPLACE] VIEW 视图名称(列名列表) AS SELECT语句[WITH [CASCADED | LOCAL] CHECK OPTION]
方式二:
ALTER VIEW 视图名称[(列名列表)] AS SELECT语句 [WITH[CASCADED | LOCAL] CHECK OPTION]
删除
DROP VIEW [IF EXISTS] 视图名称[视图名称]...
视图的检查选项
对视图进行增删改本质上就是对基表的修改,视图并不存储数据,当使用WITH CHECK OPTION子句创建视图时,MySQL会通过视图检查正在更改的每个行,例如 插入,更新,删除,以使其符合视图的定义。MySQL允许基于另一个视图创建视图,它还会检查依赖视图中的规则以保持一致性。为了确定检查的范围,mysql提供了两个选项:CASCADED和 LOCAL,默认值为 CASCADED。
CASCADED: 视图的CASCADED检查服从向下检查(这里v3不检查,v2、v1都需要检查,v2基于v1创建)
LOCAL:不会对关联视图进行检查
视图的更新
要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。如果视图包含以下任何一项,则该视图不可更新:
1.聚合函数或窗口函数(SUM()、MIN()、MAX()、COUNT()等)
2.DISTINCT
3.GROUP BY
4.HAVING
5.UNION 或者 UNION ALL
作用
简单:视图不仅可以简化用户对数据的理解,也可以简化他们的操作。那些被经常使用的查询可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件。
安全:数据库可以授权,但不能授权到数据库特定行和特定的列上。通过视图用户只能查询和修改他们所能见到的数据
数据独立:视图可帮助用户屏蔽真实表结构变化带来的影响。
存储过程
事先经过编译并存储在数据库中的一段 SQL语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。存储过程思想上很简单,就是数据库 SQL语言层面的代码封装与重用。
特点
封装,复用
可以接收参数,也可以返回数据
减少网络交互,效率提升
创建
CREATE PROCEDURE 存储过程名称([参数列表])
BEGIN
-- SQL语句
END;
调用
CALL 存储过程名称([参数列表]);
查看
-- 查询指定数据库的存储过程及状态信息
SELECT * FROM INFORMATON SCHEMA.ROUTINES WHERE ROUTINE SCHEMA='xxx';
--查询某个存储过程的定义
SHOW CREATE PROCEDURE 存储过程名称;
删除
DROP PROCEDURE [IF EXISTS] 存储过程名称;
在cmd中,执行存储过程的sql时,需要通过关键字delimiter指定sql的语句结束符。
delimiter $$ -- sql语句结束后需要用$$结束,而不是;结束
|-------|------------------|-----------------|
| 类信号 | 含义 | 备注 |
| IN | 该类型作为输入,调用时传入 | 默认 |
| OUT | 该类型作为输出,调用时作为返回值 | |
| INOUT | 该类型既作为输入,也作为输出 | 先定义进行赋值,再传入最后返回 |
CREATE PROCEDURE 存储过程名称([IN/OUT/INOUT 参数名 参数类型])
BEGIN
-- SQL语句
END;
变量:
系统变量 是MySQL服务器提供,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL)、会话变量(SESSION)
查看系统变量
SHOW [SESSION |GLOBAL] VARIABLES ; -- 查看所有系统变量
SHOW [SESSION|GLOBAL] VARIABLES LIKE '...'; --可以通过LIKE模糊匹配方式查找变量
SELECT @@[SESSION|GLOBAL] 系统变量名; --查看指定变量的值
select @@global.commit;
设置系统变量
SET [SESSION|GLOBAL] 系统变量名=值;
SET @@[SESSION|GLOBAL] 系统变量名=值;
注意:
如果没有指定SESSION/GLOBAL,默认是SESSION会话变量。
mysqI服务重新启动之后,所设置的全局参数会失效,要想不失效,可以在 /etc/my.cnf 中配置。
用户定义变量
是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用"@变量名"使用就可以。其作用域为当前连接。
赋值
SET @var_name = expr [, @var_name = expr]... ;
SET @var_name = expr [, @var_name := exprl ....;
SELECT @var_name := expr [,@var_name := expr] ... ;
SELECT 字段名 INTO @var_name FROM 表名;
使用
SELECT @var_name ;
注意: 用户定义的变量无需对其进行声明或初始化,只不过获取到的值为NULL。
局部变量
是根据需要定义的在局部生效的变量,访问之前,需要DECLARE声明。可用作存储过程内的局部变量和输入参数,局部变量的范围是在其内声明的BEGIN... END块。
声明
DECLARE 变量名 变量类型 [DEFAULT ...];
变量类型就是数据库字段类型:INT、BIGINT、CHAR、VARCHAR、DATE、TIME等
赋值
SET 变量名=值;
SET 变量名:=值;
SELECT 字段名 INTO 变量名 FROM 表名;
If
If condition1 then
... [set var1 = value1];
elseif condition2 then
... [set var2 = value2]-- 可选
else
... -- 可选
END IF;
Case
-- 要求case_value=when_value
Case case_value
When when_value1 then statement_1;
When when_value2 then statement_2;
...
Else statement_list;
END CASE;
-- 要求condition1为True
Case
When condition1 then statement_1;
When condition2 then statement_2;
...
else statement_list;
END CASE;
While
while循环是有条件的循环控制语句。满足条件后,再执行循环体中的SQL语句。
-- 先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑
WHILE 条件 DO
SQL逻辑...
END WHILE;
LOOP
LOOP 实现简单的循环,如果不在SQL逻辑中增加退出循环的条件,可以用其来实现简单的死循环。LOOP可以配合以下两个语句使用:
LEAVE:配合循环使用,退出循环。-- 退出指定标记的循环体
ITERATE:必须用在循环中,作用是跳过当前循环剩下的语句,直接进入下一次循环。-- 直接进入下一次循环
[begin label:] LOOP
SQL逻辑..
END LOOP [end label];
create procedure p(in n int)
begin
declare total int default 0;
sum:loop
if n<=0 then
Leave sum;
end if;
set total := total + n;
set n := n-1;
end loop sum;
select total;
end;
call p(10)
Cursor:
游标(CURSOR)是用来存储查询结果集的数据类型,在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游标的声明、OPEN、FETCH和 CLOSE:
声明游标
DECLARE 游标名称 CURSOR FOR 查询语句;
打开游标
OPEN 游标名称;
获取游标记录
FETCH 游标名称 INTO 变量[,变量...];
关闭游标
CLOSE 游标名称;
Create procedure p(in uage int)
begin
declare uname varchar(100);
declare upro varchar(100);
declare u_cursor cursor for select name,profession from tb_user where age <= uage,
drop table if exists tb_user_pro;
create table if not exists tb_user_pro(
id int primary key auto_increment,
name varchar(100),
profession varchar(100)
);
open u_cursor;
while true do -- 无法退出循环
fetch u cursor into uname, upro;
insert into tb_user_pro values(null, uname, upro);
end while;
close u_cursor;
END;
条件处理程序(Handler)(不知道退出条件时用这个退出,类似于python里面try...except...)
用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤:
DECLARE handler_action HANDLER FOR condition value [, condition value].... statement;
Handler_action: declare exit handler for not found close u_cursor;
CONTINUE: 继续执行当前程序
EXIT: 终止执行当前程序
condition value:
SQLSTATE sqlstate value:状态码,如 02000
SQLWARNING:所有以01开头的SQLSTATE代码的简写 警告
NOT FOUND:所有以02开头的SQLSTATE代码的简写 找不到
SQLEXCEPTION:所有没有被SQLWARNING或 NOTFOUND捕获的SQLSTATE代码的简写
存储函数
(必然会有返回值,能用存储函数完成的都能用存储过程完成)
有返回值的存储过程,存储函数的参数只有IN类型:
CREATE FUNCTION 存储函数名称([参数列表])
RETURNS type [characteristic ...] [eg:return int characteristic]
BEGIN
-- SQL语句
RETURN ...;
END;
characteristic说明:
DETERMINISTIC:相同的输入参数总是产生相同的结果
NO SQL:不包含 SQL语句。
READS SQL DATA:包含读取数据的语句,但不包含写入数据的语句
触发器
与表有关的数据库对象,指在 insert/update/delete 之前或之后,触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性,日志记录,数据校验等操作。
使用别名 OLD 和 NEW 来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还只支持行级触发,不支持语句级触发(SQL语句影响了多少条记录,触发器就会运行多少次)。
|------------|---------------------------------|
| 触发器类型 | NEW和OLD |
| insert型触发器 | NEW 表示将要或者已经新增的数据 |
| update型触发器 | OLD 表示修改之前的数据,NEW 表示将要或已经修改后的数据 |
| delete型触发器 | OLD 表示将要或者已经删除的数据 |
创建
CREATE TRIGGER trigger_name
BEFORE/AFTER INSERT/UPDATE/DELETE
ON tbl_name FOR EACH ROW -- 行级触发器
BEGIN
trigger stmt;
END;
查看
SHOW TRIGGERS;
删除 -- 如果没有指定 schema name,默认为当前数据库
DROP TRIGGER [schema_name] trigger_name;
锁
是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/0)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。
按照锁的粒度分,分为:
全局锁:锁定数据库中的所有表
表级锁:每次操作锁住整张表
行级锁:每次操作锁住对应的行数据
全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞。
其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性
FLUSH TABLES WITH READ LOCK; -- 加上只读锁
UNLOCK TABLES; -- 释放锁
加锁后:
特点
数据库中加全局锁,是一个比较重的操作,存在以下问题:
如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。
如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟。
在InnoDB引擎中,可以在备份时加上参数 --single-transaction 参数来完成不加锁的一致性数据备份。
mysqldump --single-transaction, -uroot -p123456 cast > cast.sql
表级锁
每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在MyISAM、InnoDB、BDB等存储引擎中。
对于表级锁,主要分为:
1.表锁
2.元数据锁(meta data lock,MDL)
3.意向锁
1.表锁
对于表锁,分为两类:
1.表共享读锁(read lock)
2.表独占写锁(write lock)
加锁:
lock tables表名... read/write;
释放锁:
unlock tables/客户端断开连接
读锁不会阻塞其他客户端的读,但是会阻塞写
写锁既会阻塞其他客户端的读,又会阻塞其他客户端的写
2.元数据锁(meta datalock,MDL)
MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。为了避免DML与DDL冲突,保证读写的正确性。
在MySQL5.5中引入了MDL,当对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构进行变更操作的时候,加MDL写锁(排他)。
修改表结构eg:
alter table table_name add column column_name[math] column_type[int];
查看元数据锁:
select object type,object schema,object name,lock type,lock duration from performance schema.metadata_locks ;
|---------------------------------------------|---------------------------------------|------------------------------------------|
| 对应SQL | 锁类型 | 说明 |
| lock tables xxx read/write | SHARED_READ_ONLY/SHARED_NO_READ_WRITE | |
| select 、select ... lock in share mode | SHARED READ | 与SHARED READ、SHARED WRITE兼容,与EXCLUSIVE互斥 |
| insert 、update、delete、select ... for update | SHARED WRITE | 与SHARED READ、SHARED WRITE兼容,与EXCLUSIVE互斥 |
| alter table ... | EXCLUSIVE | 与其他的MDL都互斥 |
3.意向锁
为了避免DML在执行时,加的行锁与表锁的冲突,在innoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。
线程A在一个事务中执行update 等操作时,会对表上意向锁,线程B只要检查是否有意向锁即可;否则线程B执行时需要逐行检查上锁情况。
分为
意向共享锁(IS):由语句 select ... lock in share mode添加。与表锁共享锁(read)兼容,与表锁排它锁(write)互斥
意向排他锁(IX):由insert、update、delete、select...for update 添加。与表锁共享锁(read)及排它锁(write)都互斥。
意向锁之间不会互斥。
通过SQL查看意向锁及行锁的加锁情况:
select object schema,object name,index name,lock type,lock mode,lock data from performance schema.data_locks,
行级锁
每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在InnoDB存储引擎中。
InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。
对于行级锁,主要分为以下三类:
行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行update和delete。在RC、RR隔离级别下都支持。
间隙锁(Gap Lock):锁定索引记录间隙(不含索引两端的记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持
临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。在RR隔离级别下支持
InnoDB实现了以下两种类型的行锁:
共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。
排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。
|--------------|--------|--------|
| 当前锁类型\请求锁类型 | S(共享锁) | X(排他锁) |
| S(共享锁) | 兼容 | 冲突 |
| X(排他锁) | 冲突 | 冲突 |
|------------------------------|-------|-----------------------------|
| SQL | 行锁类型 | 说明 |
| INSERT... | 排他锁 | 自动加锁 |
| UPDATE... | 排他锁 | 自动加锁 |
| DELETE... | 排他锁 | 自动加锁 |
| SELECT | 不加任何锁 | |
| SELECT... lock in share mode | 共享锁 | 在select后加lock in share mode |
| SELECT... for update | 排他锁 | 在select后加for update |
默认情况下,InnoDB在 REPEATABLE READ事务隔离级别运行,InnoDB使用 next-key锁进行搜索和索引扫描,以防止幻读。
1.针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
- InnoDB的行锁是针对于索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,此时 就会升级为表锁。
3.索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁。
4.索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询条件时,next-key退化为间隙锁。
5.索引上的范围查询(唯一索引),会访问到不满足条件的第一个值为止。
间隙锁的唯一目的是防止其他事物插入间隙,间隙锁可以共存,一个事务采用的间隙锁不会影响另一个事务在同一个间隙上采用间隙锁。
可以通过以下SOL,查看意向锁及行锁的加锁情况:
select object schema,object name, index name,lock type,lock mode,lock data from performance schema.data_locks;
内存架构-磁盘结构-后台线程-事务原理-MVCC-log-主从复制-分库分表-读写分离...