MYSQL体系结构

- 连接层:最上层是一些客户端和链接服务,主要完成一些类似于连接处理、授权认证、及相关的安全方案。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
- 服务层:第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如 过程、函数等。
- 引擎层:存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过AP!和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。
- 存储层:主要是将数据存储在文件系统之上,并完成与存储引擎的交互。
1. 存储引擎
存储引擎就是存储数据、建立索引、更新/查询数据
等技术的实现方式。
存储引擎是基于表的,而不是基于库的,所以存储引擎也可被称为表类型
。
1.1.InnoDB
InnoDB 是一种兼顾高可靠性和高性能的通用存储引擎,在 MySQL 5.5 之后,InnoDB 是默认的 MySQL 引擎。
特点:
- DML 操作(增删改)遵循 ACID 模型(原子性,一致性,隔离性,持久性),支持
事务
行级锁
,提高并发访问性能- 支持
外键
约束,保证数据的完整性和正确性
磁盘文件:
xxx.ibd
: xxx代表表名,InnoDB 引擎的每张表都会对应这样一个表空间文件,存储该表的表结构(frm、sdi)、数据和索引
查看 Mysql 变量: show variables like 'innodb_file_per_table';
如果得到的结果为ON,表示每张表都会对应这样一个表空间文件
逻辑存储结构
Row就表示数据表中的一行数据
1.2.MyISAM
MyISAM 是 MySQL 早期的默认存储引擎
特点
- 不支持事务,不支持外键
- 支持表锁,不支持行锁
- 访问速度快
磁盘文件
- xxx.sdi: 存储表结构信息
- xxx.MYD: 存储数据
- xxx.MYI: 存储索引
1.3.Memory
Memory 引擎的表数据是存储在内存
中的,受硬件问题、断电问题的影响,只能将这些表作为临时表或缓存使用
特点:
- 存放在内存中,速度快
- hash索引(默认)
磁盘文件
- xxx.sdi: 存储表结构信息
1.4.存储引擎选择

- InnoDB: 如果应用对
事务的完整性
有比较高的要求,在并发条件下要求数据的一致性
,数据操作除了插入和查询之外,还包含很多的更新、删除操作,则 InnoDB 是比较合适的选择 - MyISAM: 如果应用是
以读操作和插入操作为主
,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不高,那这个存储引擎是非常合适的。 - Memory: 将所有数据保存在内存中,访问速度快,通常用于
临时表及缓存
。Memory 的缺陷是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性
2. 索引
索引是帮助 MySQL 高效获取数据**的**数据结构(有序)
。
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查询算法,这种数据结构就是索引。
优点:
- 提高数据检索效率,降低数据库的IO成本
- 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗
缺点:
- 索引列也是要占用空间的
- 索引大大提高了查询效率,但降低了更新的速度,比如 INSERT、UPDATE、DELETE
2.1.索引结构
MySOL的索引是在存储引擎层实现
的,不同的存储引擎有不同的结构,主要包含以下几种:
索引结构 | 描述 |
---|---|
B+Tree | 最常见的索引类型,大部分引擎都支持B+树索引 |
Hash | 底层数据结构是用哈希表实现,只有精确匹配索引列的查询才有效,不支持范围查询 |
R-Tree(空间索引) | 空间索引是 MyISAM 引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少 |
Full-Text(全文索引) | 是一种通过建立倒排索引,快速匹配文档的方式,类似于 Lucene, Solr, ES |
索引 | InnoDB | MyISAM | Memory |
---|---|---|---|
B+Tree索引 | 支持 | 支持 | 支持 |
Hash索引 | 不支持 | 不支持 | 支持 |
R-Tree索引 | 不支持 | 支持 | 不支持 |
Full-text | 5.6版本后支持 | 支持 | 不支持 |
B+Tree
这里主要研究InnoDB引擎的B+Tree

与 B-Tree 的区别:
- 所有的数据都会出现在叶子节点
- 叶子节点形成一个单向链表
MySQL 索引数据结构对经典的 B+Tree 进行了优化。在原 B+Tree 的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的 B+Tree,提高区间访问的性能。

Hash
采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中
如果两个(或多个)键值,映射到一个相同的槽位上,产生了hash冲突,可以通过链表来解决
特点:
- Hash索引只能用于对等比较(=、in),不支持范围查询(betwwn、>、<、...),因为hash值是无序的
- 无法利用索引完成排序操作
- 查询效率高,通常只需要一次检索就可以了,效率通常要高于 B+Tree 索引
存储引擎支持:
- Memory
- InnoDB: 具有自适应hash功能,mysql会根据查询条件,在指定的条件下会自动的将B+Tree 索引转换为hash索引
为什么InnoDB选择B+Tree
为什么 InnoDB 存储引擎选择使用 B+Tree 索引结构?
- 相对于二叉树,层级更少,搜索效率高
- 对于 B-Tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针也跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低
- 相对于 Hash 索引,B+Tree 支持范围匹配及排序操作
2.2.索引分类
分类 | 含义 | 特点 | 关键字 |
---|---|---|---|
主键索引 | 针对于表中主键创建的索引 | 默认自动创建,只有一个 | PRIMARY |
唯一索引 | 避免同一个表中某数据列中的值重复 | 可以有多个 | UNIQUE(为数据库字段添加该约束,会自动创建唯一索引) |
常规索引 | 快速定位特定数据 | 可以有多个 | |
全文索引 | 全文索引查找的是文本中的关键词,而不是比较索引中的值 | 可以有多个 | FULLTEXT |
在 InnoDB 存储引擎中,根据索引的存储形式,又可以分为以下两种:
分类 | 含义 | 特点 |
---|---|---|
聚集索引(Clustered Index) | 将数据存储与索引放一块,索引结构的叶子节点保存了行数据 |
必须有,而且只有一个 |
二级索引(Secondary Index) | 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 | 可以存在多个 |
聚集索引选取规则:
- 如果存在主键,主键索引就是聚集索引
- 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引
- 如果表没有主键或没有合适的唯一索引,则 InnoDB 会自动生成一个
rowid
作为隐藏的聚集索引
比较两个索引:

以下 SQL 语句,哪个执行效率高?为什么?
mysqlselect * from user where id = 10; select * from user where name = 'Arm'; -- 备注:id为主键,name字段创建的有索引
答:第一条语句,因为第二条需要回表查询(使用二级索引查找'Arm'对应的id,然后再执行第一条语句),相当于两个步骤。
2.2.索引语法
创建索引:
sql
CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name (index_col_name, ...);
-- 如果不加 CREATE 后面不加索引类型参数,则创建的是常规索引
查看索引:
sql
SHOW INDEX FROM table_name;
删除索引:
sql
DROP INDEX index_name ON table_name;
2.3 SQL性能分析
一般优化的是查询操作,如果一个数据库的查询占比比较多,可以考虑对此做优化
sql执行频次
查看当前数据库的 INSERT, UPDATE, DELETE, SELECT 访问频次
SHOW GLOBAL STATUS LIKE 'Com_______';
(查询全局)
或者 SHOW SESSION STATUS LIKE 'Com_______';
(查询当前会话)
慢查询日志
慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志。
MySQL的慢查询日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:
shell
# 开启慢查询日志开关
slow_query_log=1
# 设置慢查询日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2
更改后记得重启MySQL服务,日志文件位置:/var/lib/mysql/localhost-slow.log
查看慢查询日志开关状态: show variables like 'slow_query_log';

profile
show profile 能在做SQL优化时帮我们了解时间都耗费在哪里。
通过 have_profiling 参数,能看到当前 MySQL 是否支持 profile 操作: SELECT @@have_profiling;
profiling 默认关闭,可以通过set语句在session/global级别开启 profiling: SET profiling = 1;
查看所有语句的耗时: 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 语句执行过程中表如何连接和连接的顺序。
语法:
sql
-- 直接在select语句之前加上关键字 explain / desc
EXPLAIN SELECT 字段列表 FROM 表名 HWERE 条件;
例如:查询选修了MYSQL课程的学生(子查询) EXPLAIN 各字段含义:
- id:select 查询的序列号,表示查询中执行 select 子句或者操作表的顺序(对于多表查询有多条记录,id相同,执行顺序从上到下;id不同,值越大越先执行)
- select_type:表示 SELECT 的类型,常见取值有 SIMPLE(简单表,即不适用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION中的第二个或者后面的查询语句)、SUBQUERY(SELECT/WHERE之后包含了子查询)等
- type:表示连接类型,性能由好到差的连接类型为 NULL、system、const、eq_ref、ref、range、index、all(优化sql的时候尽量往性能好的连接类型进行优化)
- possible_key:可能应用在这张表上的索引,一个或多个
- Key:实际使用的索引,如果为 NULL,则没有使用索引
- Key_len:表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好
- rows:MySQL认为必须要执行的行数,在InnoDB引擎的表中,是一个估计值,可能并不总是准确的
- filtered:表示返回结果的行数占需读取行数的百分比,filtered的值越大越好
2.4.使用规则
索引失效
情况一:最左前缀法则
如果索引关联了多列(联合索引),要遵守最左前缀法则,最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。 如果跳跃某一列,索引将部分失效(后面的字段索引失效)
。
例如:一个表有个联合索引,包含profession、age、status
查询字段:索引都不会失效
-
profession、age、status\] (索引长度54)
-
profession\] (索引长度47)


没有最左侧索引,则失效:

跳过索引,则部分失效,下面其实只有profession起效(索引长度只有47):

情况二:范围查询
联合索引中,出现范围查询(<, >),范围查询右侧的列索引失效
。
例如:status失效,因为key_len只有49 可以用>=或者<=来规避索引失效问题。
情况三:索引列运算
在索引列上进行运算操作,索引将失效,
例如:表中的phone有建立索引,查询手机号最后两位为15的用户,进行运算则会失效


情况三: 字符串类型字段使用时,不加引号,索引将失效。如:explain select * from tb_user where phone = 17799990015;
,此处phone的值没有加引号
情况四: 模糊查询中,如果仅仅是尾部模糊匹配,索引不会是失效;如果是头部模糊匹配,索引失效。如:explain select * from tb_user where profession like '%工程';
,前后都有 % 也会失效。
情况五: 用 or 分割开的条件,如果 or 其中一个条件的列没有索引,那么涉及的索引都不会被用到。
情况六: 如果 MySQL 评估使用索引比全表更慢,则不使用索引。
SQL 提示
是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操的目的。
例如,
-
使用索引:
explain select * from tb_user use index(idx_user_pro) where profession="软件工程";
(如果同时存在profession的单例索引和profession为做左侧的联合索引,数据库会选择使用联合) -
不使用哪个索引:
explain select * from tb_user ignore index(idx_user_pro) where profession="软件工程";
-
必须使用哪个索引:
explain select * from tb_user force index(idx_user_pro) where profession="软件工程";
use 是建议,实际使用哪个索引 MySQL 还会自己权衡运行速度去更改,force就是无论如何都强制使用该索引。
覆盖索引&回表查询
explain 中 extra 字段含义:
using index condition
:查找使用了索引,但是需要回表查询数据using where; using index;
:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询
回表查询
select 所需获得列中有非索引列,索引就需要到表中找到相应的列的信息,这就叫回表,先定位主键值,再定位行记录,它的性能较扫一遍索引树更低

所以,尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能找到),减少 select * ,容易出现回表查询,降低效率,除非有联合索引包含了所有字段。
面试题:
一张表,有四个字段(id, username, password, status),由于数据量大,需要对以下SQL语句进行优化,该如何进行才是最优方案:
select id, username, password from tb_user where username='itcast';
解:给username和password字段建立联合索引,则不需要回表查询,直接覆盖索引(联合索引叶子节点挂的就是id)
前缀索引
当字段类型为字符串(varchar, text等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO,影响查询效率,此时可以只降字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。
语法:create index idx_xxxx on table_name(columnn(n));
前缀长度:可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
索引设计原则
- 针对于数据量较大,且查询比较频繁的表建立索引
- 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
- 尽量选择区分度高(唯一性)的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高
- 如果是字符串类型的字段,字段长度较长,可以针对于字段的特点,建立前缀索引
- 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率
- 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价就越大,会影响增删改的效率
- 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询
3. SQL优化
3.1.插入
普通插入:
- 采用批量插入(一次插入的数据不建议超过1000条)
- 手动提交事务(如果执行多条insert时,因为mysql默认是自动提交,执行一个语句提交一次)
- 主键顺序插入
大批量插入:
如果一次性需要插入大批量数据,使用insert语句插入性能较低,此时可以使用MySQL数据库提供的load指令插入。
mysql
# 客户端连接服务端时,加上参数 --local-infile(这一行在bash/cmd界面输入)
mysql --local-infile -u root -p
# 设置全局参数local_infile为1,开启从本地加载文件导入数据的开关
set global local_infile = 1;
select @@local_infile;
# 执行load指令将准备好的数据,加载到表结构中
load data local infile '/root/sql1.log' into table 'tb_user' fields terminated by ',' lines terminated by '\n';
3.2.主键优化
在InnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(Index organized table, IOT)

叶子节点和非叶子节点都存放在"页"当中,上图中黄色的块就是页,每个页的大小是固定的16k
页分裂
页可以为空,也可以填充一半,也可以填充100%,每个页包含了2-N行数据(如果一行数据过大,会行溢出),根据主键排列。
主键顺序插入,只需要一个个往后面加就行,页空间满了后再开辟新的页空间
主键乱序插入,如果插入的那个页没有空间了,会出现页分裂的情况,会把原来的页拆分为两个给新插入的数据留空间


页合并
当删除一行记录时,实际上记录并没有被物理删除,只是记录被标记(flaged)为删除并且它的空间变得允许被其他记录声明使用。当页中删除的记录到达 MERGE_THRESHOLD(默认为页的50%,可以手动指定),InnoDB会开始寻找最靠近的页(前后)看看是否可以将这两个页合并以优化空间使用。

主键设计原则:
- 满足业务需求的情况下,尽量降低主键的长度
- 插入数据时,尽量选择顺序插入,选择使用 AUTO_INCREMENT 自增主键
- 尽量不要使用 UUID 做主键或者是其他的自然主键,如身份证号
- 业务操作时,避免对主键的修改
3.3.order by 优化
mysql排序有两种方式:
Using filesort
:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区 sort buffer 中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序Using index
:通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高
所以优化的目标是让排序操作都是通过Using index
来进行,即为order by后面的字段创建索引
比如,有个查询语句通过age,phone两个字段来排序,创建索引的时候默认都是采用升序排列,如果order by字段全部使用升序排序或者降序排序,则都会走索引,
但是如果一个字段升序排序,另一个字段降序排序,则不会走索引,explain的extra信息显示的是Using index, Using filesort
如果要优化掉Using filesort,则需要另外再创建一个索引,如:
create index idx_user_age_phone_ad on tb_user(age asc, phone desc);
,此时使用select id, age, phone from tb_user order by age asc, phone desc;
会全部走索引
总结:
- 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则
- 尽量使用覆盖索引
- 多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)
- 如果不可避免出现filesort,大数据量排序时,可以适当增大排序缓冲区大小 sort_buffer_size(默认256k)
3.4.group by优化
在分组操作时,可以通过索引来提高效率,满足最左前缀法则

如索引为idx_user_pro_age_stat
,则句式可以是select ... where profession order by age
,这样也符合最左前缀法则
3.5.limit优化
常见的问题如limit 2000000, 10
,此时需要 MySQL 排序前2000000条记录,但仅仅返回2000000 - 2000010的记录,其他记录丢弃,查询排序的代价非常大。
优化方案:一般分页查询时,通过创建覆盖索引能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化
sql
-- 此语句耗时很长
select * from tb_sku limit 9000000, 10;
-- 通过覆盖索引加快速度,直接通过主键索引进行排序及查询
select id from tb_sku order by id limit 9000000, 10;
-- 下面的语句是错误的,因为 MySQL 不支持 in 里面使用 limit
-- select * from tb_sku where id in (select id from tb_sku order by id limit 9000000, 10);
-- 通过连表查询即可实现第一句的效果,并且能达到第二句的速度
select * from tb_sku as s, (select id from tb_sku order by id limit 9000000, 10) as a where s.id = a.id;
3.6.update优化
InnoDB 的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则会从行锁升级为表锁。
update student set no = '123' where id = 1;
,这句由于id有主键索引,所以只会锁这一行;update student set no = '123' where name = 'test';
,这句由于name没有索引,所以会把整张表都锁住进行数据更新,解决方法是给name字段添加索引
4. 存储对象
4.1.视图
视图是是一种虚拟存在的表。视图中的数据并不在数据库中真实存在,行和列的数据其实是来自于自定义视图的查询中使用的表,并且是在使用视图时动态生成的
视图不保存数据,只是保存了查询的SQL逻辑,不保存查询结果,所以在创建视图的时候,主要的工作就落在创建这条SQL查询语句上
语法
- 创建语法:
CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句 [WITH | CASCADED | LOCAL | CHECK | OPTION]
; - 查看创建视图语句:
SHOW CREATE VIEW 视图名称
; - 查看视图数据:
SELECT * FROM 视图名称
; - 修改视图:
ALTER VIEW 视图名称[(列名列表)] AS SELECT语句 [WITH | CASCADED | LOCAL | CHECK | OPTION]
; - 删除视图:
DROP VIEW [IF EXISTS] 视图名称
;
sql
-- 创建
create or replace view wok_v_1 as select name,age from worker where age>18;
-- 查询视图创建的语句
show create view wok_v_1;
-- 查询视图的数据(就是后面创建语句后面跟的那条sql的结果,查询视图的时候才会执行sql)
select * from wok_v_1;
-- 修改
create or replace view wok_v_1 as select name,age,phone from worker where age>18;
-- 删除
drop view wok_v_1;
视图的检查选项
sql
create or replace view wok_v_1 as select name,age from worker where age>18;
-- 插入数据(实际上是将数据插入视图对应的表中)
insert into wok_v_1 values ('赵六',24);
-- 插入数据,但视图中查不到,因为有where限制(视图中查询不到,但是基表worker中可以查到)
insert into wok_v_1 values ('赵六',17);
-- 为了避免这种插入数据但视图中未显示的情况,我们可以创建视图时加上检查选项 with cascaded check option
create or replace view wok_v_1 as select name,age from worker where age>18 with cascaded check option;
-- 此时检查选项就会阻止我们插入数据并提示
insert into wok_v_1 values ('赵六',17);
当使用WITH CHECK OPTION
子句创建视图,MySQL会通过视图检查正在更改的每个行,例如插入、更新、删除,以使其符合视图的定义。
MySQL允许基于另一个视图(通常称为依赖视图)创建视图 ,它还会检查依赖视图中的规则以保持唯一性,为了确定检查的范围,mysql提供了两个选项:CASCADED和LOCAL,默认值为CASCADED
CASCADED:级联
在创建v2视图的时候,会去检查v1视图的条件是否满足,相当于在v1的创建视图语句也加上with cascaded check option
,换句话说需要满足所以关联视图的条件
下面的插入失败是指,在视图中插入失败,但是基表会插入成功 LOCAL
CASCADED会进行向上的传递,但是LOCAL则不会,只会检查该视图的条件,依赖视图的条件不会检查

视图的更新
不是所有视图都能进行增删改操作,视图中的行与基础表中的行之间必须存在一对一的关系
。如果视图包含以下任何一项,则该视图不可更新
- 聚合函数或窗口函数
- DISTINCT
- GROUP BY
- HAVING
- UNION或UNION ALL
作用
- 操作简单:视图不仅可以简化用户对数据的理解,也可以简化他们的操作。那些经常被使用的查询可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件
- 安全:数据库可以授权,但不能授权到数据库特定行和特定列上。通过视图用户只能查询和修改他们所能见到的数据
- 数据独立:视图可帮助用户屏蔽真实表结构变化带来的影响
4.2.存储过程
存储过程是事先经过编译并存储在数据库中的一段SQL语句的集合(例如一个业务包含多个sql语句),调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的
存储过程思想上很简单,就是数据库SQL语言层面的代码封装和复用
特点:
- 封装、复用
- 可以接受参数、也可以返回数据
- 减少网络交互、效率提升
基本语法
创建
sql
CREATE PROCEDURE 存储过程名称([参数列表])
BEGIN
SQL语句
END;
调用
CALL 名称 ([参数列表]);
查询指定数据库的储存过程及状态信息:
SELECT * FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA='数据库名';
查询某个存储过程的定义:
SHOW CREATE PROCEDURE 存储过程名;
删除:
DROP PROCEDURE [IF EXISTS] 存储过程名;
更多请看: www.bilibili.com/video/BV1Kr...
存储函数
存储函数即有返回值的存储过程,存储函数的参数只能是IN类型的
创建语法
sql
CREATE FUNCTION 存储过程名称([参数列表])
RETURNS TYPE [characteristic]
BEGIN
SQL语句
RETURN ...;
END;
characteristic(存储参数特性)说明:
- DETERMINISTIC:相同的输入参数总是产生相同的结果
- NO SQL:不包含SQL语句
- READS SQL DATA:包含读取数据的语句,但不包含写入数据的语句
例如:
sql
-- 从1到n的和
create function fun1(n int)
returns int deterministic
begin
declare total int default 0;
while n>0 do
set total := total + n;
set n := n - 1;
end while;
return total;
end;
select fun1(100);
4.3.触发器
触发器是与表有关的数据库对象,指在insert、update、delete之前或之后,触发或执行触发器中定义的SQL语句集合,触发器这种特性可以协助应用在数据库端确保数据的完整性,日志记录,数据校验等操作。
使用别名OLD和NEW来引用触发器中发生变化的记录内容,这与其他数据库是相似的,现在触发器还只支持行级触发(比如一个update语句影响了五行,则触发器会触发五次),不支持语句级触发(执行一条sql不管影响多少行,只触发一次)
基本语法 创建
sql
CREATE TRIGGER 触发器名
BEFORE/AFTER INSERT/UPDATE/DELETE
ON 表名 FOR EACH ROW -- 行级触发器
BEGIN
逻辑实现
END;
查看:SHOW TRIGGERS;
删除: DROP TRIGGER [数据库名(没有指定默认为当前数据库)] 触发器名;
案例
通过触发器记录worker表的数据变更日志,将变更日志插入到日志表user_logs中,包含增加,修改,删除;
sql
create table worker(
name char(3) comment '姓名',
phone char(11) comment '手机号',
age tinyint unsigned comment '年龄',
profession varchar(4) comment '职称',
gender char(1) comment '性别',
email varchar(16) comment '邮箱地址'
);
insert into worker values ('张三',123456,18,'经理','男','[email protected]'),
('李四',696587,20,'员工','女','[email protected]'),
('王五',642681,19,'员工','女','[email protected]'),
('张三',125462,40,'保安','男','[email protected]');
create table user_logs(
id int(11) not null auto_increment primary key ,
operation varchar(20) not null comment '操作类型',
operate_time datetime not null comment '操作时间',
operate_id varchar(11) not null comment '操作id',
operate_params varchar(500) comment '操作参数'
) comment '日志表';
-- 定义触发器
-- 插入数据触发器
create trigger wok_insert_trigger
after insert on worker for each row
begin
insert into user_logs(id, operation, operate_time, operate_id, operate_params)
values (null,'insert',now(),new.name,new.phone);
end;
-- 查看
show triggers ;
-- 插入数据
insert into worker values ('张律',126662,34,'律师','男','[email protected]');
-- 修改数据触发器
create trigger wok_update_trigger
after update on worker for each row
begin
insert into user_logs(id, operation, operate_time, operate_id, operate_params)
values (null,'update',now(),new.name,new.phone);
end;
-- 删除数据触发器
create trigger wok_delete_trigger
after delete on worker for each row
begin
insert into user_logs(id, operation, operate_time, operate_id, operate_params)
values (null,'delete',now(),old.name,old.phone);
end;
5. 锁
MySQL中的锁,按照锁的粒度分,分为以下三类:
- 全局锁:锁定数据库中的所有表。
- 表级锁:每次操作锁住整张表。
- 行级锁:每次操作锁住对应的行数据。
5.1.全局锁
全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新的事务提交语句都将被阻塞,其典型的使用场景就是做全局的逻辑备份
,对所有的表进行锁定,从而获取一致性视图,保证数据库的完整性
数据库中加全局锁,是一个比较重的操作,存在以下问题:
- 如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。
- 如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志,会导致主从延迟。
在InnoDB引擎中,可以在备份时加上参数---single-transaction
参数来完成不加锁的一致性备份
5.2.表级锁
表级锁,每次操作锁住整张表。
锁定粒度大,发生锁冲突的概率最高,并发度最低。
应用在MylSAM、InnoDB、BDB等存储引擎中。
对于表级锁,主要分为以下三类:
- 表锁
- 元数据锁( meta data lock,MDL)
- 意向锁
表锁
表锁分为两类:表共享读锁、表独占写锁
加锁:lock table 表名 read/write;
,read加的是读锁,write加的是写锁
释放锁:unlock tables; / 客户端断开连接
读锁不会阻塞其他客户端的读,但是会阻塞写。
写锁既会阻塞其他客户端的读,又会阻塞其他客户端的写。
元数据锁
元数据锁(Metadata Lock,简称MDL),MDL加锁过程是系统自动控制,无需显式使用,在访问一张表时就会自动加上。
MDL锁的主要作用是维护表元数据(表字段)的一致性,在表上有活动事务时,不可以对元数据进行写入操作。
就一句话:两个事务,其中一个事务有ALTER语句更改表结构(元数据)时,其他表的任何语句都和他互斥
意向锁
为了避免DML在执行时,加的行锁与表锁冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁(加表锁前需要挨个遍历每一行是否加了行锁),使用意向锁来减少表锁的检查。
事务对表中某些行操作(如更新或查询)时,数据库引擎自动设置相应意向锁。如果准备叫表锁的线程可能到表加了意向锁就会被阻塞,不需要一行一行去检查。
5.3行级锁
每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。
InnoDB的数据是基于索引组织的(行数据根据聚集索引存储。聚集索引存储结构的叶子节点存放的是行记录),行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁
行级锁分为三类:
- 行锁(Record Lock):锁定单行记录的锁,防止其他事务对此进行update和delete。在RC和RR隔离级别下都支持
- 间隙锁(GAP Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读,在RR隔离级别下支持
- 临键锁(Next Key Lock):行锁与间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap,在RR隔离级别下支持
行锁
行锁分为两类:
- 共享锁
- 排他锁
共享锁(S锁)
- 加锁条件:使用
SELECT ... LOCK IN SHARE MODE
语句,会锁定选中行,防止其他事务修改或删除,其他事务可读但不能改;。 - 解锁条件:默认情况下,数据被读取后,数据库系统立即解除共享锁。
- 并发性能:具有良好的并发性能,因为多个事务可以同时对同一行数据加共享锁,都能进行读取操作,相互之间不会阻塞。
排他锁(X锁)
- 加锁条件:当事务执行INSERT、UPDATE或DELETE语句时,数据库系统会自动对SQL语句操纵的数据资源使用排他锁。如果该数据资源已经有其他锁(任何锁)存在时,就无法对其再放置排他锁了。
- 解锁条件:排他锁需要等到事务结束(提交或回滚)才能被解除。
- 并发性能:最差,只允许一个事务访问锁定的数据,其他事务若要访问该数据,必须等待当前事务结束后才能获取锁。
互斥情况: 共享锁和排他锁是互斥的。
- 如果一个事务对某行数据加了共享锁,另一个事务请求对该行加排他锁时,会被阻塞,直到共享锁被释放;
- 反之,如果一个事务对某行数据加了排他锁,其他事务无论是请求加共享锁还是排他锁,都会被阻塞,直到排他锁被释放。
间隙锁
间隙锁是InnoDB存储引擎为解决幻读问题(防止其他事务在锁定的间隙中插入新数据)而设计的锁机制,它锁定的是索引记录之间的间隙,不包含记录本身
触发场景:
- 范围查询 + 排他锁:如
SELECT * FROM orders WHERE id BETWEEN 10 AND 20 FOR UPDATE;
,会锁定(10, 20)区间。 - 非唯一索引的等值查询:若查询条件涉及非唯一索引的等值匹配,且该值在表中不存在,会退化为间隙锁,如
SELECT * FROM users WHERE id = 15 FOR UPDATE;
(假设表中无id=15的记录)。 - 唯一索引的范围查询:对唯一索引执行范围查询,如
SELECT * FROM products WHERE id > 10 FOR UPDATE;
,会锁定查询范围末端之后的间隙。 - 事务隔离级别为REPEATABLE READ或SERIALIZABLE:REPEATABLE READ是默认隔离级别,会启用间隙锁;SERIALIZABLE隔离级别下,间隙锁范围扩大,完全串行化执行。
临键锁
临键锁是对索引记录的前驱或后继记录加上的锁,是行锁与间隙锁的组合,分为Next-Key Lock(行锁间隙锁)和Prev-Key Lock(行锁左开间隙锁),锁定范围是左开右闭区间
触发条件:
- 隔离级别为可重复读:在可重复读隔离级别下,InnoDB自动使用临键锁。
- 通过索引访问数据:必须通过索引条件查询,若未使用索引(全表扫描),会退化为表锁。
- 当前读操作:如SELECT ... FOR UPDATE、UPDATE、DELETE等会触发当前读,普通SELECT(快照读)不会触发临键锁。
注意:
- 索引上的等值查询,给唯一索引加锁时,临键锁退化为行锁;向右遍历时最后一个值不满足查询需求时,临键锁退化为间隙锁。
- 唯一索引的范围查询会访问到不满足条件的第一个值为止。