目录
存储引擎
MySQL体系结构
- 连接层:最上层是一些客户端和链接服务,主要完成一些类似于连接处理、授权认证、及相关的安全方案。服务器也会为安全接入的每个客户端验证它所具有的操作权限
- 服务层:第二层架构主要完成大多数的核心服务功能,如sql接口,并完成缓存的查询,sql的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在着一层实现,如过程、函数等
- 引擎层:存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以完成自己的需求,来选取合适的存储引擎。
- 存储层:主要是将数据存储在文件系统之上,并完成与存储引擎的交互。
存储引擎简介
存储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎是基于表的,而不是基于库的,所以存储引擎也可被成为表类型。一个数据库中的每个表可以是不同的存储引擎
CREATE TABLE 表名(
字段1 字段1类型,
......
) ENGINE = INNODB;
存储引擎特点
InnoDB
- 介绍:InnoDB是一种兼顾高可靠性和高性能的通用储存器,在MySQL5.5之后,是默认的mysql储存引擎
- 特点
DML 操作遵循ACID模型支持事务;
行级锁,提高并发访问性能;
支持外键FOREIGN KEY 约束,保证数据的完整性和正确性; - 文件
xxx.ibd:inoDB引擎的每张表都会对应这样一个表空间文件,存储该表的表结构(frm、sdi)、数据和索引。
参数:inodb_file_per_table
MySIAM
- 介绍:MySIAM是mysql早期的默认存储引擎
- 特点:
不支持事务,不支持外键
支持表锁,不支持行锁
访问速度快 - 文件:
xxx.sdi:存储表结构信息(可直接文本文档打开)
xxx.MYD:存储数据
xxx.MYI:存储索引
Memory
-
介绍 :memory引擎的表数据是存储在内存中的,由于受到硬件问题、或断电问题的影响,只能将这些表作为临时表或缓存使用。
-
特点:
内存存放
hash索引(默认) -
文件:
xxx.sdi:存储表结构信息
存储引擎选择
- InnoDB:是Mysql的默认存储引擎,支持事务。外键。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操作
- MyISAM:如果应用是以读操作和插入操作为主,只有很少的跟新和删除操作,并且对事务的完整性、并发性要求不是很高
- MEMORY:将所有数据保存在内存中,访问速度快,通常用于零时表及缓存。缺陷就是对表的大小有限制,太大的表无法缓存在内存中,并且无法保障安全
索引
索引概述
索引(index)是帮助MYSQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。
优势 | 劣势 |
提高数据库检索的效率,降低数据库的IO成本 | 索引列也是要占用空间的 |
通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗 | 索引大大提高了查询效率,同时却也降低更新表的速度,如对表进行INSERT、UPDATE、DELETE时,效率降低。 |
索引结构
MySql的索引实在存储引擎层实现的,不同的存储引擎有不同的结构,主要包含以下几种:
索引结构 | 描述 |
B+Tree | 最常见的索引类型,大部分引擎都支持 |
Hash索引 | 底层数据结构时用哈希表实现的,只有紧缺匹配索引列的查询才有效,不支持范围查询 |
R-tree(空间索引) | 空间索引时MyISAM引擎的一个特殊索引类型,主要用于地理空间的数据类型,通常使用较少 |
Full-text(全文索引) | 是一种通过建立倒排索引,快速匹配文档的方式。类似于Lucene,Solr,ES |
索引 | InnoDB | MyISAM | Memory |
B+tree | v | v | v |
Hash索引 | x | x | v |
R-tree | x | v | x |
Full-text | 5.6版本之后支持 | v | x |
Btree
B+Tree
相对于B-Tree区别:
- 所有的数据都会出现在叶子节点
- 叶子节点形成一个单向链表
Hash
哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中。如果多个键值,映射到一个相同的槽位上,就产生了hash冲突(hash碰撞),可以通过链表解决。
特点:
- Hash索引只能用于对等比较(=,in),不支持范围查询(between,>,<,...)
- 无法利用索引完成排序操作
- 查询效率高,通常只需要一次索引就可以,高于B+tree索引
索引分类
分类 | 含义 | 特点 | 关键字 |
主键索引 | 针对于表中主键创建的索引 | 默认自动创建,只能有一个 | PRIMARY |
唯一索引 | 避免同一个表中某数据列的值重复 | 可以有多个 | UNIQUE |
常规索引 | 快速定位特定数据 | 可以有多个 | |
全文索引 | 全文检索查找的是文本中的关键词,而不是比较索引中的值 | 可以有多个 | FULLTEXT |
innoDB存储引擎中,根据索引的储存形式,分为以下两种:
分类 | 含义 | 特点 |
聚集索引 | 将数据存储与索引放到一起,索引结构的叶子节点保存了行数据 | 必须有,且只能由一个 |
二级索引 | 将数据与索引分开存储,索引结构的叶子节点关联的是对印的主键 | 可以存放多个 |
聚集索引选取规则:
- 如果存在主键,主键索引就是聚集索引
- 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引
- 如果表没有主键,或没有合适的唯一索引,则innoDB会自动生成一个rowid作为隐藏的聚集索引
索引语法
- 创建索引: CREATE [UNIQUE | FULLTEXT] INDEX index_name ON table_name (index_col_name);
- 查看索引:SHOW INDEX FROM table_name;
- 删除索引:DROP INDEX index_name ON table_name;
SQL性能分析
SQL执行频率
Mysql客户端连接成功后,通过show [session|global] status 命令可以提供服务器状态信息。通过如下指令,可以查看当前数据库的 INSERT、UPDATE、DELETE、SELECT 的访问频次;
show global status like 'com____';
慢查询日志
记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志。MYSQL的慢查询日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:
show_query_log = 1; //1 开 0 关
long_query_time = 2; //设置慢日志的时间为2秒
profile详情
show profiles能够在做sql优化时帮我们了解时间都耗费到哪里去了。通过have_profiling参数,能够看到当前Mysql是否支持profile;
select @@have_profiling;
默认profiling是关闭的,可以通过set语句在session/global级别开启profiling;
#查看是否开启
select @@profiling;
#开启 1/关闭 0
SET [global|session] 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语句执行过程中表如何连接和连接的顺序。
语法:
explain select 字段列表 from 表名 where 条件;
执行计划名字段含义:
- 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的值越大越好。
索引使用
最左前缀法则
如果索引了多列(联合索引),要遵守最左前缀法则,即从索引的最左列开始,并且不跳过索引中的列。如果跳过某一列,索引将部分失效(后面的字段索引失效)。
如果查询条件没有city_id 将不走索引,如果查询条件有city_id、phone 将走两个索引,如果查询条件有city_id、username只走city_id索引。
范围查询
联合索引中,出现范围查询(>,<),范围查询右侧的列索引失效,尽量使用(>=,<=)
select * from users where city_id=10 AND phone > 10000 AND username="test"(只会走到city_id索引,后面的索引失效)
select * from users where city_id=10 AND phone >= 10000 AND username="test"(索引都有效)
索引列运算
不要在索引列上进行运算操作,否则索引失效
select * from users where substring(phone,10,2)='15'; (索引失效)
字符串不加引号
字符串类型字段使用时,不加引号,索引将失效。
select * from users where username= 123;(索引失效)
模糊查询
如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效
select * from users where phone like "%156%";(索引失效)
or连接的条件
用or分割开的条件,如果or前的条件中的字段有索引,而后面的字段中没有索引,那么设计的索引都不会被用到(都有索引才会有索引,有一个没有索引,就没有索引)
select * from users where phone=“15823232323” and age=20;(索引失效,因为age没有加入索引)
select * from users where id=10 and age=20;(索引失效,因为age没有加入索引)
数据分布影响
如果mysql评估使用索引比全表更慢,则不适用索引。
select * from users where phone >=0;(全表扫描可能更快,索引失效)
SQL提示
是优化数据库的一个重要手段,简单来说,就是在sql语句中加入一些认为的提示来达到优化操作的目的。
- use index:select * from users use index(user_city_id) where phone ="1523213213";(告诉数据库用哪个索引)
- ignore index:select * from users ignore index(user_city_id) where phone ="1523213213";(告诉数据库不用哪个索引)
- force index:select * from users force index(user_city_id) where phone ="1523213213";(告诉数据库必须用哪个索引)
覆盖索引
尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到),减少select * 。
如果在extra中出现了:
- useing index condition :查找使用了索引,但是需要回表查询数据(慢)
- using where; using index:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据(快)
前缀索引
当字段类型为字符串(varchar,text等)时,有时候需要索引很长的字符串,这回让索引变得很大,查询时,良妃大量的磁盘IO,影响查询效率。此时可以将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。
create index idx xxx on table_name(column(n));
前缀长度:可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高,为索引的选择性时1,这是最好的索引选择性,性能也是最好的。
#查看手机号的选择性
select count(distinct phone)/count(*) from users;
#查看截取手机号的选择性 如果字符串越少 越接近1 性能越好
select count(distinct substring(phone,1,5))/count(*) from users;
#比如选择形式1 字符串最少为5 创建索引
create index index_phone on users(phone(5));
单列索引与联合索引
单列索引:即一个索引只包含单个列。
联合索引:即一个索引包含了多个列。(由于最左侧前缀法则,创建的时候要考虑顺序)
在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引。
多条件查询时,mysql优化器会评估那个字段的索引效率更高,会选择该索引完成本次查询
索引设计原则
- 针对于数据量较大,查询比较频繁的表建立索引。
- 针对于常作为查询条件(where)、排序(sort by)、分组(group by)操作的字段建立索引。
- 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
- 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
- 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。
- 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。
- 如果索引列不能储存null值,请在创建时使用 not null 约束它,当优化器知道每列是包含null值时,它可以更好的确定哪个索引最有效的用于查询。
sql优化
插入数据
建议批量插入
一次建议不超过1千条,超过了用手动提交事务
insert into 表名 values (xxx,xxx),(xxx,xxx),(xxx,xxx);
手动提交事务
start transcation;
insert into 表名 values(val1,val2),(val1,val2);
insert into 表名 values(val1,val2),(val1,val2);
insert into 表名 values(val1,val2),(val1,val2);
commit;
主键顺序插入
顺序插入 id: 1 2 3 4 5 6 7 8 9 10
乱序插入 id:2 1 3 6 7 9 10 8 5 4
大批量插入数据
如果一次性需要插入大批量数据,使用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 'users' fields terminated by ',' lines terminated by '\n';
主键优化
数据组织方式
在InnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(IOT)
页分裂
页可以为空,也可以填充一半,也可以填充100%,每个页包含了2-N行数据(如果一行数据过大,就会溢出),根据主键排列。(主键乱序插入肯能存在也分裂现象)
页合并
当删除一行记录时,实际上记录并没有被物理删除,只是记录被标记(flaged)为删除并且它的空间变得允许被其他记录声明使用。
当液中的删除记录达到merge_threshold(合并页的阈值,可以自己设置,在创建表或者创建索引时指定,默认为页的50%),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间
主键设计原则
- 满足业务需求的情况下,尽量减低主键的长度。
- 插入数据时,尽量选择循序插入,选择使用AUTIO_INCREMENT自增主键
- 尽量不要使用UUID做主键或者时其他自然主键,如身份证号。
- 业务操作时,避免对主键的修改。
order by优化
- Using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort buffer 中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫FileSort排序。
- Using index:通过有序索引顺序扫描直接返回有序数据,这种情况即为using index,不需要额外排序,操作效率高。(效率高于 using filesort)
#没有创建索引时,根据age,phone进行排序
explain select id,age,phone from users order by age,phone;(using filesort)
#创建age,phone联合索引
create index idx_age_phone on users(age,phone);
#创建索引后,根据age,phone进行升序排序
explain select id,age,phone from users order by age,phone; (using index)
#创建索引后,根据age,phone进行降序排序
explain select id,age,phone from users order by age desc,phone desc; (using index)
#根据age,phone进行一个降序,一个升序排序
explain select id,age,phone from users order by age asc,phone desc;(using filesort)
#创建索引
create index idx_age_asc_phone_desc on users(age asc,phone desc);
#根据age,phone进行一个降序,一个升序排序
explain select id,age,phone from users order by age asc,phone desc;(using index)
- 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则。
- 尽量使用覆盖索引。
- 多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)。
- 如果不可避免的出现filesort,大数据量排序时,可以适当增大排序缓冲区大小sort_buffer_size(默认256k)。
group by优化
- 在分组操作时,可以通过索引来提高效率。
- 分组操作时,索引的使用也是满足最左前缀法则的。
limit优化
一个常见又非常头疼的问题就是limit 2000000,10 此时需要mysql排序前2000010 记录,仅仅返回2000000 - 2000010 的记录,其他记录丢失,查询排序的代价非常大。
优化思路:一般分页查询时,通过创建覆盖索引能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化,如:
select * from pre_plan pl1,(select id from pre_plan limit 5000000,50) pl2 where pl1.id=pl2.id;
count优化
- MyISAM 引擎把一个表的总行数存在磁盘上,因此执行count(*) 的时候会直接返回这个数,效率很高;
- InnoDB引擎,执行count(*)的时候,需要把数据一行一行地从引擎里面读出来,然后累计计数;
优化思路:自己计数(如radis,新增记录+1 删除记录-1)
count的集中用法
- count() 是一个聚合函数,对于返回的结果集,一行行地判断,如果count函数的参数不是NULL,累计值就加1,否则不加,最后返回累计值
- 用法:count(*) count(主键) count(字段) count(1)
- count(主键):把每一行的主键ID值都取出来,返回给服务层。服务层拿到主键后,直接按行进行累加(主键不可能为null)
- count(字段):
没有not null约束 — InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null,计数累加。
有not null约束 — InnoDB引擎会遍历整张表把每一行的字段都取出来,返回给服务层,直接按行进行累加。 - count(1):InnoDB引擎遍历整张表,但不取值,服务层对于返回的每一行,放一个数字“1”进去,直接按行进行累加。
- count(*):
InnoDB引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加。
效率排序:count(字段) < count(主键id) < count(1) ≈ count(*),所以尽量使用count(*)
update优化
InnoDB的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则会从行锁升级为表锁,从而导致并发性能降低,所以对于更新的条件一定要加索引
视图
介绍
是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。(不保存数据,只保存sql逻辑,所以创建视图时,主要的工作就是创建sql查询语句)
创建
create [ or replace ] view 视图名称 [ (列名列表) ] AS SELECT 语句 [ with [ cascaded | local ] check option ]
查询
查看创建视图语句:SHOW CRWATE 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
create view v1 as select * from users where id<20
create view v2 as select * from v1 where id>10 with cascaded check option;(如果创建v2视图关联了v1视图,插入数据不光要检查是否满足v2还要检查是否满足v1)
create view v3 as select * from v3 where id <15 ;(创建该视图如果执行 insert info v3 values(27,"test") ,会执行失败,因为该插入v3无限制,v3继承于v2,也满足v2,但v2有cascaded,故不满足v1 所以失败 )
local
create view v1 as select * from users where id<15
create view v2 as select * from v1 where id>10 with local check option;(如果创建v2视图关联了v1视图,插入数据只检查是否满足v2)
create view v3 as select * from v3 where id <20 ;(创建该视图如果执行 insert info v3 values(14,"test") ,会执行成功,因为该插入v3无限制,v3继承于v2,也满足v2,因v2 是 local ,v1 无条件所以成功 )
更新
要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。如果视图包含以下任一项,都不可跟新:
- 聚合函数 sum min max count 等
- distinct
- group by
- having
- union 或 union all
作用
- 简单,不仅可以简化用户对数据的理解,也可以简化操作,那些被经常使用的查询可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件
- 安全,诗剧苦可以授权,但不能授权到数据库特定行的特定列上。通过视图用户只能查询和修改他们锁看见到的数据
- 数据独立,视图可以帮助用户屏蔽正式表结构变化带来的影响
存储过程
介绍
存储过程是实现经过编译并存储在数据库中的一段sql语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据库和应用服务器之间的传输,对于提高数据处理的效率
封装与重用sql语言
- 特点:封装、复用,可以接收参数,也可以返回数据,减少网络交互,提升效率
创建
#创建结束符
delimiter $$;
#创建存储过程
create procedure 存储过程的名称([参数列表])
begin
-- sql语句
end;$$
在命令行中,执行创建储存过程的sql时,徐奥通过关键字delimiter指定sql语句的结束符
调用
call 名称([参数]);
查看
#查询指定数据库的存储过程及状态信息
select * from information_schema.routines where routine_schema='xxx';
#查询某个存储过程的定义
show create procedure 存储过程名称;
删除
drop procedure [ if exists] 存储过程名称;
变量
系统变量
是mysql服务器提供,不是用户定义的,属于服务器层面,分全局变量(GLOBAL)、会话变量(SESSION)。
- 查看系统变量
show [ session|global ] variables; 查看所有系统变量 show [ session|global ] variables like "......"; 可以通过like模糊匹配方式查找变量 select @@[ session|global ] 系统变量名; 查看指定变量的值
- 设置系统变量
set [ session|global ] 系统变量名 = 值; set @@[ session|global ] 系统变量名 = 值;
自定义变量
是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用“@变量名”使用就可以。其作用域为当前连接。
- 赋值
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;
局部变量
是根据需要定义的在局部生效的变量,访问之前,需要declare声明。可用作存储过程内的局部变量和输入参数,局部变量的范围是在其内声明的begin ...end块。
- 声明
declare 变量名 变量类型 [default ...]; 变量类型就是数据库字段类型:int、begint、char、varchar、date、time等。
- 赋值
set 变量名 = 值; set 变量名 :=值; select 字段名 into 变量名 from 表格...;
参数
类型 | 含义 | 备注 |
in | 该类参数作为输入,也就是调用时传入值 | 默认 |
out | 该类参数作为输出,也就是该参数可以作为返回参数 | |
inout | 既可以作为输入参数,也可以作为输出参数 |
if条件语句
if 条件1 then
......
elseif 条件2 then
......
else
......
end if;
语法:
create procedure 储存过程名称([in/out/inout 参数名 参数类型])
begin
sql语句
end;
case条件语句
CASE case_value:
WHEN when_value1 THEN statement_list1
WHEN when_value2 THEN statement_list2
...
ELSE statement_other
END CASE;
while循环
#先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑
while 条件 do
sql 逻辑...
end while;
repeat 循环
repeat是有条件的循环控制语句,当满足条件的时候推出循环。
#先执行一次逻辑,然后判定逻辑是否满足,如果满足,则退出。如果不满足,则继续下一次循环
REPEAT
sql逻辑
UNTIL 条件
END REPEAT;
loop循环
loop 实现简单的循环,如果不在sql逻辑中增加退出循环的条件,可以用其来实现简单的死循环,loop可以配合以下两个语句使用:
- leave:配合循环使用,退出循环。(类似于php中的break;)
- iterate:必须用在循环中,作用是跳过当前循环剩下的语句,直接进入下一次循环。(类似于php中的continue;)
label:LOOP
sql逻辑...
[leave label]
[iterate label]
END LOOP label;
游标
游标是用来存储查询结果集的数据类型,在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游标的声明、OPEN、FETCH和CLOSE。
- 声明游标
DECLARE 游标名称 CURSOR FOR 查询语句; - 打开游标
OPEN 游标名称; - 获取游标记录
FETCH 游标名称 INTO 变量; - 关闭游标
CLOSE 游标名称;
条件处理程序
可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤。
DECLARE handler_action HANDLAR FOR condition_value... statement;
handler_action
CONTINUE:继续执行当前程序
EXIT:终止执行当前程序
condition_value
SQLSTATE sqlstate_value:状态码
SQLWARNING: 所有以01开头的sqlstate代码的简写
NOT FOUND: 所有以02开头的SQLSTATE代码的简写
SQLEXCEPTION: 所有没有以SQLWARNING 或 NOT FOUND 捕获的sqlstate代码的简写
存储函数
存储函数是有返回值的存储过程,存储函数的参数只能是IN类型的
CREATE FUNCTION 存储函数名称([参数列表])
RETURNS type [characteristic ...]
BEGIN
--sql语句
RETURN...;
END;
#characteristic说明
# DETERMINISTIC: 相同的输入参数总是产生相同的结果
# NO SQL: 不包含 SQL 语句
# READS SQL DATA: 包含读取数据的语句,但不包含写入数据的语句。
触发器
与表有关的数据库对象,指在insert/update/delete 之前或之后,触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助使用在数据库端确保数据的完整性,日志记录,数据校验等操作。
使用别名OLD和NEW来引用触发器中发生变化的记录内容,这与其他的数据库是相似的,现在触发器还只支持行级出发,不支持语句级触发。
触发器类型 | 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;
#删除
DROP TRIGGER [schema_name] trigger_name; (如果没有指定schema_name,默认为当前数据库)
锁
锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,除传统的计算资源(CPU,RAM,I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性,有效性是所有数据库必须解决的一个问题,锁冲突也是印象数据库并发访问性能的一个重要因素。从这个角度来说,所对于数据库而言非常重要,也更加复杂。
按照锁的粒度来分:
- 全局锁:锁定数据库中的所有表。
- 表级锁:每次操作锁住整张表。
- 行级锁:每次操作锁住对应的行数据。
全局锁
全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经跟新操作的事务提交语句都将被阻塞。(可读,不可新增,删除,修改)
使用场景:做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。
flush tables with read lock;(添加全局锁)
unlock tables; (释放锁)
特点:
- 如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。
- 如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binglog),会导致主从延迟。
在InnoDB引擎中,我们可以在备份时加上参数 --single-transaction 参数来完成不加锁的一致性数据备份。
mysqldump --single-transaction -uroot -p123456 users >users.sql
表级锁
表级锁,每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在MyISAM、InnoDB、BOB等存储引擎中。
表锁
表共享读锁(read lock)阻止当前客户端的写,阻塞其他客户端的写
表独占写锁(write lock)当前客户端可读可写,其他客户端不可读不可写
#加锁
lock tables 表名... read/write
#释放锁
unlock tables / 客户端断开连接
元数据锁 MDL
MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据一致性,在表上有活动事务的时候,不可以对元数据经行写入操作。(某张表有未提交的事务,就不能修改数据)
作用:避免DML与DDL冲突,保证读写的正确性。
在mysql5.5中引入了MDL,党对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构进行变更操作的时候,加MDL写锁(排他)。
对应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 | sharead_write | 与shared_read、sharead_write兼容,与exclusive互斥 |
alter table ... | exclusive | 与其他的mdl都互斥 |
查看元数据锁:
select 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 ... form update 添加,与表锁共享锁(read)及排它锁(write)都互斥,意向锁之间不会互斥。
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):允许获取排他锁的事务更新数据,阻止其他事务获取相同数据集的共享锁和排他锁。
共享锁和共享锁兼容,排它锁跟共享锁、排它锁都不兼容。
sql | 行锁类型 | 说明 |
insert ... | 排他锁 | 自动加锁 |
update ... | 排他锁 | 自动加锁 |
delete ... | 排他锁 | 自动加锁 |
select ... | 不加任何锁 | |
select ... lock in share mode | 共享锁 | 手动 |
select ... for update | 排他锁 | 搜东 |
默认情况下,InnoDB在repeatable read 事务隔离级别运行,innoDB使用next-key锁进行搜索和索引扫描,防止幻读。
- 针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化行锁。
- InnoDB的行锁是正对于索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,此时升级为表锁
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
间隙锁 / 临键锁 - 演示
默认情况下,InnoDB在REPEATABLE TABLE READ事务隔离级别运行,InnoDB使用next-key锁进行搜索和索引扫描,防止幻读。
1、索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁。
2、索引上的等职查询(普通索引),向右遍历时最后一个值不满足查询需求时,next-key lock 退化为间隙锁。
3、索引上的范围查询(唯一索引)--会访问到不满足条件的第一个值为止。
innoDB引擎
逻辑存储结构
- 表空间(ibd),一个mysql实例可以对应多个表空间,用于存储记录、索引等数据。
- 段,分为数据段(leaf node segment),索引段(Non-leaf node segment),回滚段(Rollback segment),innoDB时索引组织表,数据段就是B+数的叶子节点,索引段即为B+树的非叶子节点,段用来管理多个Extent(区)
- 区,表空间的单元结构,每个去的大小为1M,默认情况下,innoDB存储引擎页大小为16k,即一个区中一共由64个连续的页。
- 页,是innoDB存储引擎磁盘管理的最小单元,每个页的大小默认为16k,为了保证页的连续性,InnoDB存储引擎每次从磁盘申请4-5个区。
- 行,InnoDB存储引擎数据是按照行进行存放的。
架构
内存结构
Buffer Pool
缓冲池是主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改查操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘加载并缓存),然后再以一定频率刷新到磁盘,从而减少磁盘10,加快处理速度。
缓冲池以Page页为单位,底层采用链表数据结构管理Page。根据状态,将Page分为三种类型:
- free page:空闲page,未被使用。
- clean page:被使用page,数据没有被修改过
- dirty page:脏页,被使用page,数据被修改过,也中数据与磁盘的数据产生了不一致。
Change Buffer
更改缓冲区(针对于非唯一二级索引页),在执行DML语句时,如果这些数据Page没有在Buffer Pool中,不会直接操作磁盘,而会将数据变更存在更改缓冲区Change Buffer 中,在未来数据被读取时,再将数据合并恢复到BufferPool中,再将合并后的数据刷新到磁盘中。
Change Buffer的意义是什么?
与聚集索引不同,二级索引通常是非唯一的,并且以相对随机的顺序插入二级索引。同样,删除和更新可能会影响索引树中不相邻的二级索引页,如果每一次都操作磁盘,会造成大量的磁盘!0。有了ChangeBuffer之后,我们可以在缓冲池中进行合并处理,减少磁盘I0
Adaptive Hash Index
自适应hash索引,用于优化对Buffer Pool数据的查询。InnoDB存储引擎会监控对表上各索引页的查询,如果观察到hash索引可以提升速度,则建立hash索引,称之为自适应hash索引。
自适应哈希索引,无需人工干预,是系统根据情况自动完成。
参数:adaptive_hash_index
Log Buffer
日志缓冲区,用来保存要写入到磁盘中的log日志数据(redolog、undolog),默认大小为 16MB,日志缓冲区的日志会定期剧新到磁盘中。如果需要更新、插入或删除许多行的事务,增加日志缓冲区的大小可以节省磁盘1/0。
参数:
innodb_log_buffer_size:缓冲区大小
innodb_flush_log_at_trx_commit:日志刷新到磁盘时机
磁盘结构
System Tablespace
系统表空间是更改缓冲区的存储区域。如果表是在系统表空间而不是每个表文件或通用表空间中创建的,它也可能包含表和索引数据。(在MVSQL5.x版本中还包含InnoDB数据字典、undolog等)
参数:innodb data file path
File-Per-Table Tablespaces
每个表的文件表空间包含单个InnoDB表的数据和索引,并存储在文件系统上的单个数据文件中。
参数:innodb_file_perYable
GeneralTablespaces
通用表空间,需要通过 CREATE TABLESPACE 语法创建通用表空间,在创建表时,可以指定该表空间。
CREATE TABLESPACE xxx ADD DATETIME 'file_name' ENGINE = engine_name;
Undo Tablespaces
撤销表空间,MVSQL实例在初始化时会自动创建两个默认的undo表空间(初始大小16M),用于存储undo log日志。
Temporary Tablespaces
InnoDB 使用会话临时表空间和全局临时表空间。存储用户创建的临时表等数据。
Doublewrite Buffer Files
双写缓冲区,innoDB引擎将数据页从Buffer Pool刷新到磁盘前,先将数据页写入双写缓冲区文件中,便于系统异常时恢复数据。
Redo Log
重做日志,是用来实现事务的持久性。该日志文件由两部分组成:重做日志缓冲(redo logbuffer)以及重做日志文件(redolog),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都会存到该日志中,用于在刷新脏页到磁盘时,发生错误时,进行数据恢复使用。
事务原理
MVCC
mysql管理
mysql数据库安装完成后,自带了4个数据库:
数据库 | 含义 |
mysql | 存储mysql服务器正常运行所需要的各种信息(时区,主从,用户,权限) |
information_schema | 提供了访问数据库元数据的各种表和视图,包含数据库、表、字段类型及访问权限等 |
performance_schema | 为mysql服务器运行时状态提供了一个底层监控功能,主要用于手机数据库服务器性能参数 |
sys | 包含了一系列方便DBA和开发人员利用performance_schema性能数据库进行性能调优和诊断的视图 |