MySQL笔记-进阶

MySQL体系结构

  • 连接层

最上层是一些客户端和链接服务,主要完成一些类似于连接处理、授权认证、及相关的安全方案。服务器也会为安全接入的每个客户端验证它所具有的操作权限。

  • 服务层

第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。

  • 引擎层

存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。

  • 存储层

主要是将数据存储在文件系统之上,并完成与存储引擎的交互。

存储引擎

InnoDB

InnoDB 是一种兼顾高可靠性和高性能的通用存储引擎,在 MySQL 5.5 之后,InnoDB 是默认的 MySQL 引擎。

特点:

  • DML 操作遵循 ACID 模型,支持事务
  • 行级锁,提高并发访问性能
  • 支持外键约束,保证数据的完整性和正确性

文件:

  • xxx.ibd: xxx代表表名,InnoDB 引擎的每张表都会对应这样一个表空间文件,存储该表的表结构(frm、sdi)、数据和索引。

参数:innodb_file_per_table,决定多张表共享一个表空间还是每张表对应一个表空间

InnoDB 逻辑存储结构:

MyISAM

MyISAM 是 MySQL 早期的默认存储引擎。

特点:

  • 不支持事务,不支持外键
  • 支持表锁,不支持行锁
  • 访问速度快

文件:

  • xxx.sdi: 存储表结构信息
  • xxx.MYD: 存储数据
  • xxx.MYI: 存储索引

Memory

Memory 引擎的表数据是存储在内存中的,受硬件问题、断电问题的影响,只能将这些表作为临时表或缓存使用。

特点:

  • 存放在内存中,速度快
  • hash索引(默认)

文件:

  • xxx.sdi: 存储表结构信息

存储引擎的选择

在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。

  • InnoDB: 如果应用对事物的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操作,则 InnoDB 是比较合适的选择
  • MyISAM: 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不高,那这个存储引擎是非常合适的。
  • Memory: 将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。Memory 的缺陷是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性

电商中的足迹和评论适合使用 MyISAM 引擎,缓存适合使用 Memory 引擎。

索引

索引是帮助 MySQL 高效获取数据数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查询算法,这种数据结构就是索引。

优缺点:

优点:

  • 提高数据检索效率,降低数据库的IO成本
  • 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗

缺点:

  • 索引列也是要占用空间的
  • 索引大大提高了查询效率,但降低了更新的速度,比如 INSERT、UPDATE、DELETE

索引结构

索引结构 描述
B+Tree 最常见的索引类型,大部分引擎都支持B+树索引
Hash 底层数据结构是用哈希表实现,只有精确匹配索引列的查询才有效,不支持范围查询
R-Tree(空间索引) 空间索引是 MyISAM 引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少
Full-Text(全文索引) 是一种通过建立倒排索引,快速匹配文档的方式,类似于 Lucene, Solr, ES
索引 InnoDB MyISAM Memory
B+Tree索引 支持 支持 支持
Hash索引 不支持 不支持 支持
R-Tree索引 不支持 支持 不支持
Full-text 5.6版本后支持 支持 不支持

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

CleanShot 2024-05-02 at 17.53.53@2x

面试题

  1. 为什么 InnoDB 存储引擎选择使用 B+Tree 索引结构?
  • 相对于二叉树,层级更少,搜索效率高
  • 对于 B-Tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针也跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低
  • 相对于 Hash 索引,B+Tree 支持范围匹配及排序操作

索引分类

分类 含义 特点 关键字
主键索引 针对于表中主键创建的索引 默认自动创建,只能有一个 PRIMARY
唯一索引 避免同一个表中某数据列中的值重复 可以有多个 UNIQUE
常规索引 快速定位特定数据 可以有多个
全文索引 全文索引查找的是文本中的关键词,而不是比较索引中的值 可以有多个 FULLTEXT

在 InnoDB 存储引擎中,根据索引的存储形式,又可以分为以下两种:

分类 含义 特点
聚集索引(Clustered Index) 将数据存储与索引放一块,索引结构的叶子节点保存了行数据 必须有,而且只有一个
二级索引(Secondary Index) 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 可以存在多个

聚集索引选取规则:

  • 如果存在主键,主键索引就是聚集索引
  • 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引
  • 如果表没有主键或没有合适的唯一索引,则 InnoDB 会自动生成一个 rowid 作为隐藏的聚集索引

Pasted image 20240502183340

语法

创建索引:
CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name (index_col_name, ...);
如果不加 CREATE 后面不加索引类型参数,则创建的是常规索引

查看索引:
SHOW INDEX FROM table_name;

删除索引:
DROP INDEX index_name ON table_name;

性能分析

查看执行频次

查看当前数据库的 INSERT, UPDATE, DELETE, SELECT 访问频次:
SHOW GLOBAL STATUS LIKE 'Com_______'; 或者 SHOW SESSION STATUS LIKE 'Com_______';

注:7个下划线,因为INSERT有6个字符,查的目标是类似com_insert

慢查询日志

慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志。

MySQL的慢查询日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:

  • 开启慢查询日志开关
    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 语句执行过程中表如何连接和连接的顺序。

语法:

直接在select语句之前加上关键字 explain / desc
EXPLAIN SELECT 字段列表 FROM 表名 HWERE 条件;

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
  • possible_key:可能应用在这张表上的索引,一个或多个
  • Key:实际使用的索引,如果为 NULL,则没有使用索引
  • Key_len:表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好
  • rows:MySQL认为必须要执行的行数,在InnoDB引擎的表中,是一个估计值,可能并不总是准确的
  • filtered:表示返回结果的行数占需读取行数的百分比,filtered的值越大越好

使用规则

最左前缀法则

如果索引关联了多列(联合索引),要遵守最左前缀法则,最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。
如果跳跃某一列,索引将部分失效(后面的字段索引失效)。

联合索引中,出现范围查询(<, >),范围查询右侧的列索引失效。可以用>=或者<=来规避索引失效问题。

索引失效情况

  1. 在索引列上进行运算操作,索引将失效。如:explain select * from tb_user where substring(phone, 10, 2) = '15';
  2. 字符串类型字段使用时,不加引号,索引将失效。如:explain select * from tb_user where phone = 17799990015;,此处phone的值没有加引号
  3. 模糊查询中,如果仅仅是尾部模糊匹配,索引不会是失效;如果是头部模糊匹配,索引失效。如:explain select * from tb_user where profession like '%工程';,前后都有 % 也会失效。
  4. 用 or 分割开的条件,如果 or 其中一个条件的列没有索引,那么涉及的索引都不会被用到。
  5. 如果 MySQL 评估使用索引比全表更慢,则不使用索引。

SQL 提示

是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。

例如,使用索引:
explain select * from tb_user use index(idx_user_pro) where 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就是无论如何都强制使用该索引。

覆盖索引&回表查询

尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能找到),减少 select *

在辅助索引中找聚集索引,如select id, name from xxx where name='xxx';,只需要一次查询,因为会根据name索引出id,此时直接就可以返回id,name。

但是如果select id, name, gender from xxx where name='xxx';就会出现回表查询,因为根据name只能查到id,需要再根据id查询gender,性能会降低。

前缀索引

当字段类型为字符串(varchar, text等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO,影响查询效率,此时可以只降字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。

语法:create index idx_xxxx on table_name(column(n));
选择column的前n个字符做索引

前缀长度:可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。

求选择性公式:

1
2
3
4
5
select count(distinct email) / count(*) from tb_user;  
select count(distinct substring(email, 1, 10)) / count(*) from tb_user;
select count(distinct substring(email, 1, 8)) / count(*) from tb_user;
select count(distinct substring(email, 1, 5)) / count(*) from tb_user;
-- 一点点尝试,选择一个合适的

单列索引&联合索引

单列索引:即一个索引只包含单个列
联合索引:即一个索引包含了多个列
在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引。

单列索引情况:
explain select id, phone, name from tb_user where phone = '17799990010' and name = '韩信';
这句只会用到phone索引字段

注意事项
  • 多条件联合查询时,MySQL优化器会评估哪个字段的索引效率更高,会选择该索引完成本次查询

设计原则

  1. 针对于数据量较大,且查询比较频繁的表建立索引
  2. 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
  3. 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高
  4. 如果是字符串类型的字段,字段长度较长,可以针对于字段的特点,建立前缀索引
  5. 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率
  6. 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价就越大,会影响增删改的效率
  7. 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询

SQL 优化

插入数据

普通插入:

  1. 采用批量插入(一次插入的数据不建议超过1000条)
  2. 手动提交事务
  3. 主键顺序插入

大批量插入:
如果一次性需要插入大批量数据,使用insert语句插入性能较低,此时可以使用MySQL数据库提供的load指令插入。

1
2
3
4
5
6
# 客户端连接服务端时,加上参数 --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行数据(如果一行数据过大,会行溢出),根据主键排列。

页分裂现象:mysql的数据是以页为单位保存的,当按顺序存储时,会一点点按顺序把页填满,但是当我们不按顺序中间添加了一个,就会单独创建一个页,将第一页的后50%放进新页中,再放入不按顺序的主键,再把整个新页放在原来第一页第二页中间,这样效率比较低。

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

  • MERGE_THRESHOLD:合并页的阈值,可以自己设置,在创建表或创建索引时指定

主键设计原则:

  • 满足业务需求的情况下,尽量降低主键的长度
  • 插入数据时,尽量选择顺序插入,选择使用 AUTO_INCREMENT 自增主键
  • 尽量不要使用 UUID 做主键或者是其他的自然主键,如身份证号
  • 业务操作时,避免对主键的修改

order by优化

  1. Using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区 sort buffer 中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序
  2. Using index:通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高

总结:

  • 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则
  • 尽量使用覆盖索引
  • 多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)
  • 如果不可避免出现filesort,大数据量排序时,可以适当增大排序缓冲区大小 sort_buffer_size(默认256k)

group by优化

  • 在分组操作时,可以通过索引来提高效率
  • 分组操作时,索引的使用也是满足最左前缀法则的

limit优化

常见的问题如limit 2000000, 10,此时需要 MySQL 排序前2000000条记录,但仅仅返回2000000 - 2000010的记录,其他记录丢弃,查询排序的代价非常大。
优化方案:一般分页查询时,通过创建覆盖索引能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化

1
2
3
4
5
6
-- 效率低
select * from tb_sku limit 9000000, 10;
-- 覆盖索引
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;

count优化

count的几种用法

  • count(主键):InnoDB引擎会遍历整张表,把每行的主键id值都取出来,返回给服务层,服务层拿到主键后,直接按行进行累加(主键不可能为空)
  • count(字段):没有not null约束的话,InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null,计数累加;有not null约束的话,InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,直接按行进行累加
  • count(1):InnoDB 引擎遍历整张表,但不取值。服务层对于返回的每一层,放一个数字 1 进去,直接按行进行累加
  • count(*):InnoDB 引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加

按效率排序:count(字段) < count(主键 id) < count(1) ≈ count(*),所以尽量使用 count(*)

update优化

InnoDB 的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则会从行锁升级为表锁。

如以下两条语句:
update student set no = '123' where id = 1;,这句由于id有主键索引,所以只会锁这一行;
update student set no = '123' where name = 'test';,这句由于name没有索引,所以会把整张表都锁住进行数据更新,解决方法是给name字段添加索引

视图

视图(View)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。
通俗的讲,视图只保存了查询的SQL逻辑,不保存查询结果。所以我们在创建视图的时候,主要的工作就落在创建这条SQL查询语句上。

作用

  • 简单
    视图不仅可以简化用户对数据的理解,也可以简化他们的操作。那些被经常使用的查询可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件。

  • 安全
    数据库可以授权,但不能授权到数据库特定行和特定的列上。通过视图用户只能查询和修改他们所能见到的数据

  • 数据独立
    视图可帮助用户屏蔽真实表结构变化带来的影响。

创建视图

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 QPTION子句创建视图时,MySQL会通过视图检查正在更改的每个行,例如插入,更新,删除,以使其符合视图的定义。MySQL允许基于另一个视图创建视图,它还会检查依赖视图中的规则以保持一致性。为了确定检查的范围,mysql提供了两个选项:CASCADED 和 LOCAL ,默认值为 CASCADED。

LOCAL&CASCADED

会检查当前视图的条件,同时会向上看,如果依赖的视图开了检查选项也会检查,没开就不检查,CASCADED 不管上面开没开检查选项都会检查。

更新

要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。如果视图包含以下任何一项,则该视图不可更新

  1. 聚合函数或窗口函数 ( SUM()、MIN()、MAX()、COUNT() 等 )
  2. DISTINCT
  3. GROUP BY
  4. HAVING
  5. UNION 或者UNION ALL

存储过程

存储过程是事先经过编译并存储在数据库中的一段SQL 语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。 存储过程思想上很简单,就是数据库SQL 语言层面的代码封装与重用。

特点

  1. 封装、复用
  2. 可以接收参数,也可以返回数据
  3. 减少网络交互,效率提升

基本语法

创建

1
2
3
4
5
6
7
CREATE PROCEDURE 存储过程名称( [参数列表] ) 

BEGIN

SQL 语句

END;

调用

CALL 名称 ([参数])

查看

查询指定数据库的存储过程及状态信息

SELECT* FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'xxx'

存储过程名称;–查询某个存储过程的定义

SHOW CREATE PROCEDURE

删除

DROP PROCEDURE [ IFEXISTS ] 存储过程名称

变量

系统变量

系统变量是MySQL服务器提供,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL)、会话变量(SESSION)。

查看系统变量

1
2
3
4
5
SHOW [SESSION|GLOBAL] VARIABLES; -- 查看所有系统变量

SHOW [SESSION | GLOBAL] VARIABLES LIKE '……'--可以通过LIKE模糊匹配方式查找变量

SELECT @@[SESSION|GLOBAL] 系统变量名; -- 查看指定变量的值

设置系统变量

1
2
3
SET [SESSION|GLOBAL] 系统变量名=值;

SET @@[SESSION|GLOBAL]系统变量名=值;

注意:

如果没有指定SESSION/GLOBAL,默认是SESSION,会话变量。

mysql服务重新启动之后,所设置的全局参数会失效,要想不失效,可以在 /etc/my.cnf 中配置。

用户定义变量

用户定义变量是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用“@变量名”使用就可以。其作用域当前连接。

赋值

1
2
3
4
5
6
7
SET @var_name =expr [,@var_name=expr]...;

SET @var_name := expr [,@var_name := expr]...;

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等。

赋值

1
2
3
4
5
SET 变量名=值;

SET 变量名:=值;

SELECT 字段名 INTO 变量名 FROM 表名….;

if

语法:

1
2
3
4
5
6
7
8
9
10
11
12
IF 条件1 THEN

……

ELSEIF 条件2 THEN -- 可选

……

ELSE --可选

END IF;

参数

类型 描述 输入 输出
IN 该参数仅用于输入
OUT 该参数仅用于输出
INOUT 该参数可以用于输入和输出
用法:
1
2
3
4
5
6
7
CREATE PROCEDURE 存储过程名称([ IN/OUT/INOUT 参数名参数类型])

BEGIN

-- SQL语句

END ;

case

语法一

1
2
3
4
5
6
7
CASE case_value

WHEN when_valuel THEN statement _listl
[WHEN when_value THEN statement_list 2]…
[ELSE statement_list]

END CASE;

语法二

1
2
3
4
5
6
7
CASE

WHEN searchycondition] THEN statement_listl
[WHEN search_condition2 THEN statement_list2]…
[ELSE statement_list]

END CASE;

while

while 循环是有条件的循环控制语句。满足条件后,再执行循环体中的SQL语句。具体语法为:

1
2
3
4
5
6
7
--先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑

WHILE 条件 DO

SQL逻辑…

END WHILE;

repeat

repeat是有条件的循环控制语句,当满足条件的时候退出循环。具体语法为:

1
2
3
4
5
6
7
8
9
--先执行一次逻辑,然后判定逻辑是否满足,如果满足,则退出。如果不满足,则继续下一次循环

REPEAT

SQL逻辑…

UNTIL 条件

END REPEAT;

loop

LOOP实现简单的循环,如果不在SQL逻辑中增加退出循环的条件,可以用其来实现简单的死循环。LOOP可以配合一下两个语句使用:

•LEAVE:配合循环使用,退出循环。

•ITERATE:必须用在循环中,作用是跳过当前循环剩下的语句,直接进入下一次循环。

1
2
3
4
5
[begin _label:] LOOP

SQL逻辑…

END LOOP [end_label];
1
2
LEAVE Lagel;--退出指定标记的循环体 
ITERATE label;-- 直接进入下一次循环

游标

游标(CURSOR)是用来存储查询结果集的数据类型,在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游标的声明、OPEN、FETCH和CLOSE,其语法分别如下。

声明游标

DECLARE 游标名称 CURSOR FOR 查询语句;

打开游标

OPEN 游标名称;

获取游标记录

FETCH 游标名称 INTO 变量[变量];

关闭游标

CLOSE 游标名称;

条件处理程序

条件处理程序(Handler)可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤。具体语法为:

DECLARE handler action HANDLER FOR condition value L condition value]..statement

handler_action
CONTINUE:继续执行当前程序
EXIT:终止执行当前程序

condition_value :

`SQLSTATE sqlstate_value:状态码,如02000`

`SQLWARNING:所有以01开头的SQLSTATE代码的简写`

`NOT FOUND:所有以02开头的SQLSTATE代码的简写`

`SQLEXCEPTION:所有没有被SQLWARNING或NOT FOUND捕获的SQLSTATE代码的简写`

具体状态码可以参考官方文档

触发器

触发器是与表有关的数据库对象,指在insert/update/delete之前或之后,触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性,日志记录,数据校验等操作。
使用别名OLD和NEW来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。

触发器类型 NEW 和 OLD
INSERT NEW 表示将要或者已经新增的数据
UPDATE OLD表示修改之前的数据,NEW表示将要或已经修改后的数据
DELETE OLD表示将要或者已经删除的数据
  • 语法

创建

1
2
3
4
5
6
7
8
9
10
11
CREATE TRIGGER trigger_name

BEFORE/AFTER INSERT/UPDATE/DELETE

ON tbl_name FOR EACH ROW --行级触发器

BEGIN

trigger_stmt ;--SQL逻辑

END;

查看

SHOW TRIGGERS ;

删除

DROP TRIGGER [schema_name.]trigger_name;–如果没有指定 schema_name,默认为当前数据库。

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。

NOTE : 针对事物才有加锁的意义。

分类:MySQL中的锁,按照锁的粒度分,分为以下三类:

  1. 全局锁:锁定数据库中的所有表。
  2. 表级锁:每次操作锁住整张表。
  3. 行级锁:每次操作锁住对应的行数据。

全局锁

全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞。
其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。

表锁

表级锁,每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在MyISAM、InnoDB、BDB等存储引擎中。

对于表级锁,主要分为以下三类:

  1. 表锁:对于表锁,分为两类:1.表共享读锁(read lock)所有的事物都只能读(当前加锁的客户端也只能读,不能写),不能写 2.表独占写锁(write lock),对当前加锁的客户端,可读可写,对于其他的客户端,不可读也不可写。
    读锁不会阻塞其他客户端的读,但是会阻塞写。写锁既会阻塞其他客户端的读,又会阻塞其他客户端的写。

  2. 元数据锁(meta data lock,MDL),MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。在MySQL5.5中引入了MDL,当对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构进行变更操作的时候,加MDL写锁(排他)。

  3. 意向锁: 为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。
    一个客户端对某一行加上了行锁,那么系统也会对其加上一个意向锁,当别的客户端来想要对其加上表锁时,便会检查意向锁是否兼容,若是不兼容,便会阻塞直到意向锁释放。

意向锁兼容性:

  1. 意向共享锁(IS):与表锁共享锁(read)兼容,与表锁排它锁(write)互斥。
  2. 意向排他锁(lX):与表锁共享锁(read)及排它锁(write)都互斥。意向锁之间不会互斥。

行锁

行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在InnoDB存储引擎中。
InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。对于行级锁,主要分为以下三类:

  1. 行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行update和delete。在RC(read commit )、RR(repeat read)隔离级别下都支持。
  2. 间隙锁(GapLock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持。比如说 两个临近叶子节点为 15 23,那么间隙就是指 [15 , 23],锁的是这个间隙。
  3. 临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。在RR隔离级别下支持。

InnoDB实现了以下两种类型的行锁:

  1. 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。
  2. 排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。
SQL 行锁类型 说明
insert 排他锁 自动加锁
update 排他锁 自动加锁
delete 排他锁 自动加锁
select 不加任何锁
select LOCK IN SHARE MODE 排他锁 需要手动在SELECT之后加LOCK IN SHARE MODE
select FOR UPDATE 排他锁 需要手动在SELECT之后加FOR UPDATE