其实很多名词,把他们拆开的意思组合起来,就可以大致明白组合成的名词是什么意思。就拿存储引擎来说,存储在计算机中的意思是将数据保存到某种介质中,并且保证数据的正常访问。而引擎的是指发动机的核心。
组合起来的意思大致是保存数据的核心技术,也就是说,存储引擎是服务于存储服务的,通过存储引擎将数据保存。就跟计算机如何将数据保存到磁盘中一样,在数据库中,存储引擎的意思就是通过何种引擎将数据存储在磁盘中。
存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎是基于表的,而不是基于库的,所以存储引擎也可被称为表类型。
CREATE TABLE 表名(
字段1 字段1类型[COMMENT 字段1注释],
……
字段n 字段n类型[COMMENT 字段n注释]
)ENGINE = INNODB [COMMENT 表注释];
SHOW ENGINES;
例子:
1、
show engines;
create table my_myisname(
id int ,
name varchar(10)
)engine = MyISAM;
InnoDB是一种兼顾搞可靠性和高性能的通用存储引擎,在MySQL5.5之后,InnoDB是默认的存储引擎
特点:
事务
;行级锁
,提高并发访问性能;外键
FOREIGN KEY 约束,保证数据的完整性和正确性文件:
XXX.ibd:XXX代表的是表名,innoDB引擎的每张表都会对应这样一个表空间文件,存储该表的结构(frm、sdi)、数据和索引
参数:innodb_file_per_table
比如:
show variables like 'innodb_file_per_table';
可见该表是打开着的,每一张表都对应着一个表空间
注意:查看ProgramData的时候如果一开始没有,打开显示隐藏文件就行
不能直接点击去看,因为其是二进制文件
打开cmd,输入ibd2sdi account.ibd
InnoDB 的逻辑存储结构:
相当于图书馆->书架->书本->书页->一行字->一个字
MyISAM是MySQL早期的默认存储引擎
特点:
文件:
例如:
用notepad++打开sdi文件,负责立马的数据,再用浏览器打开json.cn显示
Memory引擎的表数据时存储再内存中的,由于受到硬件问题、或断电问题的影响,只能将这些表作为临时或缓存使用
特点:
文件:
xxx.sdi:存储表结构信息
存储引擎的区别:
在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合
InnoDB:是Mysql的默认存储引擎,支持事务、外键
。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操作,那么InnoDB存储引擎是比较合适的选择
MyISAM:如果应用是以读操作和插入操作为主
,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。比如:业务系统当中的日志相关数据、电商当中的足迹、评论的相关数据等
MEMORY:将所有数据保存在内存中,访问速度快
,通常用于临时表及缓存。MEMORV的缺陷就是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性
学习linux操作
看黑马MySQL p65
注意:
如果没有连接失败了,那么就在虚拟机端口里面输入
systemctl stop firewalld
,之后出现弹窗输入虚拟机的密码即可关闭防火墙
索引(index)是帮助MySQL高效获取数据
的数据结构
(有序
)。在数据之外,数据库系统还维护着满足特点查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引
备注:上述二叉树索引结构的只是一个示意图,并不是真实的索引结构
优势 | 劣势 |
---|---|
提高数据索引的效率,降低数据库的IO成本 | 索引列也是要占用空间的 |
通过索引列对数据进行排列,减低数据排列的成本,降低CPU的消耗 | 索引大大提高了查询效率,同时却也降低更新表的速度,如对表进行INSERT、UPDATE、DELETE时,效率降低 |
MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的结构,主要包含以下几种:
索引结构 | 描述 |
---|---|
B+Tree索引 | 最常见的索引类型,大部分引擎都支持B+树索引 |
Hash索引 | 底层数据结构是用哈希表实现的,只有精确匹配索引列的查询才有效,不支持范围查询 |
R-tree(空间索引) | 空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少 |
Full-text(全文索引) | 是一种通过建立倒排索引,快速匹配文档的方式。类似于Lucenne,Solr,ES |
我们平常所说的索引,如果没有特别指明,都是指B+树结构组织的索引
数据结构可视化的网址 Data Structure Visualizations
0345就会变成中间元素,它就会向上分裂
插入元素超过最大度数的限制时会再次向上分裂,会把中间元素放到最上面的节点中
B+tree是B树的变种
以一个最大度数为4的B+tree为例
B+tree的特点:
B+tree的生成:
和btree类似,也是超出范围(最大度数)后中间元素向上分裂,但是,不同的地方在于,B+tree向上分裂的同时,会把中间元素留在下面(留在叶子节点),同时叶子节点形成链表,向上分裂时,叶子节点以向上的元素为分裂点进行分裂
以一颗最大度数为5的B+tree为例:
在上面的图中插入1234,就会变成下面的样子
MySQL索引数据结构对经典的B+tree进行了优化,在原B+tree的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+tree,提高区间访问
的性能
说到页就要指针inodb的逻辑存储结构:表空间、段、区、页、行,而且一个页大小在innodb当中默认是16k
哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后储存在hash表中
如果两个(或多个)键值,映射到一个相同的槽位上,他们就形成了hash冲突(也成为hash碰撞),可以通过链表来解决
hash索引的特点:
思考题:
为什么InnoDB储存引擎选择使用B+tree索引结构?
分类 | 含义 | 特点 | 关键字 |
---|---|---|---|
主键索引 | 针对于表中主键创建的索引 | 默认自动创建,只能有一个 | PRIMARY |
唯一索引 | 避免同一个表中某数据列中的值重复 | 可以有多个 | UNIQUE |
常规索引 | 快速定位特点数据 | 可以有多个 | |
全文索引 | 全文索引查找的是文本中的关键字,而不是比较索引中的值 | 可以有多个 | FULLTEXT |
特点 | 含义 | 特点 |
---|---|---|
聚集索引(Clustered Index) | 将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据 | 必须有,而且只有一个 |
二级索引(Secondary Index) | 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 | 可以存在多个 |
聚集索引选取规则:
这里id为主键,所以id就是聚集索引,id形成的聚集索引就是上图的第一个B+tree结构,叶子节点存储的就是id所对应的那一行的数据,例如,id=5的叶子节点对应的就是第一行的数据,
第二个B+tree结构对应的就是二级索引,二级索引的叶子节点所对应的数据就是主键值
如果查询时不是通过主键查询,那么就会先走二级索引查到对应的主键值,再通过对应的主键值在聚集索引中查找到对应的行元素这个过程就叫作回表查询
第一个执行效率高,因为他直接通过主键查找元素,
直接使用聚集索引查找数据,而第二条要先在二级索引找到对应的id值,再通过id值在聚集索引中查找对应的数据(回表查询)
同理,三层就会有1171*16kb=21939856kb的存储空间
CREATE [UNIQUE | FULLTEXT] INDEX index_name ON table_name(index_col_name,...);
SHOW INDEX FROM table_name;
DROP INDEX index_name ON table_name;
mysql> select *from tb_user_s1;
+----+-----------+-------------+---------------+-----------------+------+--------+--------+---------------------+
| id | name | phone | email | profession | age | gender | status | createtime |
+----+-----------+-------------+---------------+-----------------+------+--------+--------+---------------------+
| 1 | 鲁班 | 12345678910 | 123@qq.com | 软件工程 | 22 | 1 | 3 | 2012-02-01 00:00:00 |
| 2 | 华佗 | 12645678910 | 12@qq.com | 互联网工程 | 22 | 1 | 3 | 2012-02-02 00:00:00 |
| 3 | 张飞 | 12343678910 | 1@qq.com | 土木工程 | 22 | 1 | 3 | 2012-02-03 00:00:00 |
| 4 | 关羽 | 12345678910 | 13@qq.com | 计算机 | 22 | 1 | 3 | 2012-02-04 00:00:00 |
| 5 | 刘备 | 12315678910 | 23@qq.com | 软件工程 | 22 | 1 | 3 | 2012-02-11 00:00:00 |
| 6 | 猴子 | 12342678910 | 1234@qq.com | 化学工程 | 22 | 1 | 3 | 2012-02-21 00:00:00 |
| 7 | 压缩 | 12345678910 | 1223@qq.com | 通讯工程 | 22 | 1 | 3 | 2012-02-06 00:00:00 |
| 8 | 剑圣 | 12345678610 | 1123@qq.com | 软件工程 | 22 | 1 | 3 | 2012-02-12 00:00:00 |
| 9 | 刀妹 | 12345698910 | 12344@qq.com | 国际贸易 | 22 | 1 | 3 | 2012-02-15 00:00:00 |
| 10 | 小法师 | 12245678910 | 13235@qq.com | 软件工程 | 22 | 1 | 3 | 2012-02-09 00:00:00 |
| 11 | 佐伊 | 12345678910 | 12213@qq.com | 园林工程 | 22 | 1 | 3 | 2012-02-11 00:00:00 |
| 12 | 狼人 | 12345658910 | 124653@qq.com | 软件工程 | 22 | 1 | 3 | 2012-02-21 00:00:00 |
| 13 | 男刀 | 12385678910 | 12203@qq.com | 软件工程 | 22 | 1 | 3 | 2012-02-11 00:00:00 |
| 14 | vn | 32345678910 | 12398@qq.com | 人工智能 | 22 | 1 | 3 | 2012-02-21 00:00:00 |
| 15 | 卡沙 | 12325678910 | 12013@qq.com | 应用数学 | 22 | 1 | 3 | 2012-02-21 00:00:00 |
| 16 | 烬 | 12345378910 | 12023@qq.com | 物理 | 22 | 1 | 3 | 2012-02-19 00:00:00 |
+----+-----------+-------------+---------------+-----------------+------+--------+--------+---------------------+
16 rows in set (0.00 sec)
索引的名称:一般为idx_表名_字段名
-- 1.
create index idx_user_name on tb_user_s1(name);
-- 2.
create unique index idx_user_phone on tb_user_s1(phone);
-- 3. 创建联合索引的顺序是有讲究的
create idnex idx_user_pro_age_sta on tb_user_s1(profession,age,status);
-- 4.删除索引
create index idx_user_emaill on tb_user_s1(email);
drop index idx_user_email on tb_user_s1;
MySQL客户端连接成功后,通过show[session | global] status 命令可以提供服务器状态信息。通过如下指令,可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次
SHOW GLOBAL STATUS LIKE 'Com_______';
例子:
慢查询日志记录了所有执行时间超过指定参数(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
vi /etc/my.cnf切换,然后按G切换到最后操作慢查询日志
退出后重启systemctl restart mysqld
,可见开关打开了
-- 先用cd切换到这个目录下
cd /var/lib/mysql
-- 然后执行下面这条语句就可以实时查看慢查询日志,
-- 只要有执行时间超过指定时间的,都会被慢查询日志记录
tail -f localhost-slow.log
show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。通过have_profiling参数,能够看到当前MySQL是否支持profile操作:
SELECT @@have_profiling;
默认profiling是关闭的,可以通过set语句在session/global级别开启profiling;
SET profiling = 1;
#1.查看每一条SQL的耗时基本情况
show profiles;
#2.查看指定query_id的SQL语句各个阶段的耗时情况
show profile for query query_id;
#3.查看指定query_id的语句CPU的使用情况
show profile cpu for query query_id;
通过explain或者desc命令可以获取mysql如何执行select语句的信息,包括select语句执行过程中表如何连接和连接的顺序
语法:
#直接在select语句之前加上关键字explain/desc
EXPLAN SELECT 字段列表 FROM 表名 WHERE 条件;
查看每一个学生的选课情况
select s.*,c.* from student s,coures c,student_course sc where s.id = sc.studentid and o.id = sc.course;
explain select s.*,c.* from student s,coures c,student_course sc where s.id = sc.studentid and o.id = sc.course;
下图中sql的执行顺序就是c-》sc-》< subquery2 >-》s
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的值越大越好
Extra(主要关注的字段)
额外信息
SELECT*FROM tb_sku WHERE sn = '100000003145001';
一条数据的查询耗时了21秒的时间,这个性能极低
为什么性能极低呢?
由于id是主键,主键默认的是由主键索引的
create index indx_sku_sn on tb_sky(sn);
SELECT*FROM tb_sky WHERE sn = '100000003145001';
如果一个索引关联了多个字段(联合索引),在使用时就要遵循最左前缀法则,
最左前缀法则:查询从索引的最左列开始,如果没有最左边的列,那么就不能使用索引查询,如果存在最左边的列,但是查询时查询条件跳过了索引中的某一列,那么索引将部分失效(最左边列之后的索引都会失效)
例如:
-- 可以使用索引查询,因为最左边的索引存在且没有跳过任何一个列
-- 这里这三个条件的位置可以改变,不会影响,但是最左的字段必须存在
explain select *from tb_user_s1
where profession='软件工程' and age=22 and status='3';
+----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-----------------------+
| 1 | SIMPLE | tb_user_s1 | NULL | ref | idx_user_pro_age_status | idx_user_pro_age_status | 73 | const,const,const | 6 | 100.00 | Using index condition |
+----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-----------------------+
1 row in set, 1 warning (0.01 sec)
-- possible_keys显示可能使用的索引
-- key显示实际使用的索引
-- key_len 显示索引使用的字节数
-- 不能使用索引查询,因为在联合索引中间age没有查询,所以查询时不能使用索引
mysql> explain select *from tb_user_s1 where age=22 and status='3'
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb_user_s1 | NULL | ALL | NULL | NULL | NULL | NULL | 16 | 6.25 | Using where |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
-- 这里最左边列一旦没有出现,之后的索引就全部失效了
-- 使用的查询方式是全部遍历
mysql> explain select *from tb_user_s1 where profession='软件工程' and status='3';
+----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | tb_user_s1 | NULL | ref | idx_user_pro_age_status | idx_user_pro_age_status | 63 | const | 6 | 10.00 | Using index condition |
+----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
-- 索引当中最左边的字段存在就可以了,跟所放的位置无关
-- 这里age后的status索引会失效
-- 使用>=不会失效,只有使用>或<时才会失效
mysql> explain select *from tb_user_s1 where profession='软件工程' and age>20 and status='3';
+----+-------------+------------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | tb_user_s1 | NULL | range | idx_user_pro_age_status | idx_user_pro_age_status | 68 | NULL | 6 | 10.00 | Using index condition |
+----+-------------+------------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
不要在索引列上进行运算操作,否则索引将会失效
select *from tb_user_s1 where substring(phone,10,2);
-- 这里就使用了substring进行函数运算,
-- 在查询时就没有使用索引,而是全表扫描,性能降低
;-- 字符串加了单引号
mysql> explain select *from tb_user_s1 where phone='12385678910'
-- type是ref,代表使用索引查询
+----+-------------+------------+------------+------+----------------+----------------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+----------------+----------------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | tb_user_s1 | NULL | ref | idx_user_phone | idx_user_phone | 45 | const | 1 | 100.00 | Using index condition |
+----+-------------+------------+------------+------+----------------+----------------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
-- 字符串没加单引号
mysql> explain select *from tb_user_s1 where phone=12385678910;
-- type是all,代表没有使用索引查询
+----+-------------+------------+------------+------+----------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+----------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb_user_s1 | NULL | ALL | idx_user_phone | NULL | NULL | NULL | 16 | 10.00 | Using where |
+----+-------------+------------+------------+------+----------------+------+---------+------+------+----------+-------------+
1 row in set, 3 warnings (0.00 sec)
注意:
复合索引使用时如果没有使用最左侧的索引,那么索引就不会生效
-- 这里phone是普通索引,而age是联合索引,由于没有联合索引的最左列字段,所以age这个字段没有建立索引
-- 可以看作没有索引,此时在进行查询时,索引都没有生效
mysql> explain select *from tb_user_s1 where phone='12345698910' or age=22;
+----+-------------+------------+------------+------+----------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+----------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb_user_s1 | NULL | ALL | idx_user_phone | NULL | NULL | NULL | 16 | 16.92 | Using where |
+----+-------------+------------+------------+------+----------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
-- phone和name都是普通索引,or的两边都有索引,所以索引查询生效
mysql> explain select *from tb_user_s1 where phone='12345698910' or name='刀妹';
+----+-------------+------------+------------+-------------+------------------------------+------------------------------+---------+------+------+----------+--------------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------------+------------------------------+------------------------------+---------+------+------+----------+--------------------------------------------------------+
| 1 | SIMPLE | tb_user_s1 | NULL | index_merge | idx_user_name,idx_user_phone | idx_user_phone,idx_user_name | 45,43 | NULL | 2 | 100.00 | Using union(idx_user_phone,idx_user_name); Using where |
+----+-------------+------------+------------+-------------+------------------------------+------------------------------+---------+------+------+----------+--------------------------------------------------------+
1 row in set, 1 warning (0.01 sec)
例如:在查询某些数据时,这个表中的大多数数据都符合要求,那么就不会使用索引,而是用全表扫描,因为MySQL认为全表扫描比索引更快
-- 这里is null就会使用索引,因为mysql认为表中的数据大多数都不是null,所以使用索引
mysql> explain select *from tb_user_s1 where profession is null;
+----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | tb_user_s1 | NULL | ref | idx_user_pro_age_status | idx_user_pro_age_status | 63 | const | 1 | 100.00 | Using index condition |
+----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.01 sec)
-- 这里就不会使用索引,还是因为mysql认为表中的数据大多数都不是null,所以不走索引
mysql> explain select *from tb_user_s1 where profession is not null;
+----+-------------+------------+------------+------+-------------------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+-------------------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb_user_s1 | NULL | ALL | idx_user_pro_age_status | NULL | NULL | NULL | 16 | 100.00 | Using where |
+----+-------------+------------+------------+------+-------------------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
不用SQL提示时,当一个字段有多个索引,那么,MySQL会自动的选择一个索引使用,例如,profession有一个普通索引和联合索引,那么,在查询时MySQL会自动选择使用联合索引去查询。
而使用SQL提示就可以使SQL按照指定的索引去查询
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 = '软件工程';
尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到),减少select *
简单理解就是,在select之后的字段尽量都有索引,查询时就不会回表查询了
mysql> explain select *from tb_user_s1 where profession='软件工程' and age=22 and status='3' ;
+-----------------------+
| Extra |
+-----------------------+
| Using index condition |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select id,profession,age,status from tb_user_s1 where profession='软件工程' and age=22 and status='3' ;
+--------------------------+
| Extra |
+--------------------------+
| Using where; Using index |
+--------------------------+
1 row in set, 1 warning (0.00 sec)
可以看到,使用select*
查询时出现的是Using index condition
,证明出现的回表查询,效率不是最高的,而查询时,select后是联合索引对应的字段时,出现的是Using where; Using index
证明是直接通过索引查询,效率较高
覆盖索引,不需要回表,查询两个字段,查询后直接就把数据返回,没有再进去表中查询,一次索引扫描就完成了
没有覆盖索引,要先扫描辅助索引(二级索引),获取id值后还要再去扫描聚集索引才能获取想要的数据,索引扫描了两次,效率自然就低了,进行回表查询:
使用select*
就会很容易回表查询,所以要避免使用select*
当字段类型为字符串(varchar,text等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO,影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率
-- idx_xxxx:索引名
-- table_name:表名
-- column(n)字段名,n代表提取前几个字符作为前缀索引,例如,n=5,
-- 那么就会使用字符串的前5个字符构建索引
create index idx_xxxx on table_name(colunm(n));
求选择性的方法:
例如
select count(distinc email) / count(*) from tb_user;
-- 这里就是用substring来判断选择度,下面的代码就是截取从1索引开始,前5个字符,来判断一个
-- 字段前5个字符的选择度
-- 用这段代码就可以获取选择性,进而判断选择前几个字符作为前缀来创建索引
select count(distinct substring(email,1,5)/count(*) from tb_user;
前缀索引是用来解决一些长字符串或者是大文本字段,在整个字段进行索引的时候、索引体积过于庞大而造成浪费大量磁盘IO的情况,可以使用前缀索引
单列索引情况:
explain select id,phone,name from tb_user where phone = '17799990010' and name = '韩信';
一次查询使用多个单列索引时,只会使用一个单列索引,另外的单列索引不会使用
在业务场景中,如果存在多个查询条件,针对多个字段建立索引时,建议使用联合索引,而不是单列索引
多条件联合查询时,MySQL优化器会评估哪个字段的索引效率更高,会选择该索引完成本次查询
联合索引情况:
phone和name有联合索引,每个字段放前面还是放后面对查询是有影响的
尽量使用联合索引,避免使用单列索引,创建联合索引时注意创建的字段顺序
例子:
insert into tb_test values(1,'tom');
insert into tb_test values(2,'cat');
insert into tb_test values(3,'jerry');
....
INSERT INTO 表名 (字段名1,字段名2,...) VALUES (值一,值二,...),(值一,值二,...),(值一,值二,...);.....
INSERT INTO 表名 VALUES (值一,值二,...),(值一,值二,...),(值一,值二,...);``
但是批量插入一次最多不要超过1000条,大概就是500到1000条,如果一次性要插入几万条数据,那么可以将其分为多条insert语句插入
start transaction;
insert into 表名 values 具体数据1;
insert into 表名 values 具体数据2;
insert into 表名 values 具体数据3;
commit;
主键乱序插入:8 1 9 21 88 2 4 15 89 5 7 3
主键顺序插入:1 2 3 4 5 7 8 9 15 24 88 89
使用load指令的步骤:
1.在客户端连接服务端的时候,加上参数 – local-infile
,以此查看全局参数
mysql --local-infile -u root -p
2.设置全局参数local_infile为1,开启从本地加载文件导入数据的开关
set gloabl local_infile = 1;
3.执行load指令将准备好的数据,加载到表结构中
load data local infile '/root/sql1.log' into table 'tb_user' fields terminated by ','lines terminated by '\n';
在finalshell导入的文件
查看该脚本所处位置是在root目录下的
注意这里插入数据时,由于是虚拟机,所以要现在finalshall中上传数据,把数据上传到虚拟机中
这里插入100万条数据只需要耗时16秒,很强
使用load插入时也需要主键顺序插入,顺序插入数据高于乱序插入
主键顺序插入性能高于乱序插入
主键的时候乱序插入:
不会这样子插入,因为叶子节点是有序的,50插入进来就应该存放在47后面这个位置
那么再来看1号数据页虽然没写满了,但是50这一行数据
写不下了,它会开启一个新的数据页,但是50
不会直接写到这个数据页上,会做一个操作:
开启一个新的数据页,然后会找到第一个数据页50%的位置——23和47这两行数据移动到新开辟的数据页当中
然后再将50插入到这个new数据页当中
此时需要再对链表指针再进行一个重新的设置,它会设置1号数据页它的下一个数据页是3号,而3号数据页的下一个是2号
那么这种主键乱序插入的情况现象称之为——页分裂
, 这个是比较消耗性能的
当页中删除的记录达到MERGE_THRESHOLD(默认为页的50%
),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间使用
当下个数据页还有50%以上是空闲的,所以这2号和3号数据页InnoDB引擎就会自动进行合并
Using filesort
:通过表的索引或全表扫描,读取满足条件的数据行,然后再排序缓冲区sort buffer
中完成排序操作,因此所有不是通过索引直接返回排序结果的排序都叫FileSort排序
Using index
:通过有序索引顺序扫描直接返回有序数据,这种情况即为using index
,不需要额外排序,操作效率搞#没有创建索引时,根据age,phone进行排序
explain select id,age,phone from tb_user order by age,phone;
#创建索引
create index idx_user_age_phone_aa on tb_user(age,phone);
#创建索引后,根据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;
例如:
-- 此时age和phone都没有索引,使用order by排序时都是Using filesort,效率较低
mysql> explain select id,age ,phone from tb_user_s1 order by age;
+----------------+
| Extra |
+----------------+
| Using filesort |
+----------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select id,age ,phone from tb_user_s1 order by age,phone;
+----------------+
| Extra |
+----------------+
| Using filesort |
-+----------------+
1 row in set, 1 warning (0.00 sec)
-- 为age和phone建立联合索引后,就会变为Using index
-- 若在查询时,order by之后的联合索引的排序不同,例如,一个顺序,一个倒序,
-- 也会出现Using filesort的情况
-- 这里age升序排列,phone倒序排列
select id,age ,phone from tb_user_s1 order by age asc,phone desc;
-- 这种情况的解决方式就是在创建联合索引时就把顺序定好
-- 下面就是在创建联合索引时就确定age和phone的排列方式
create index idx_user_age_phone_ad on tb_user_s1(age asc ,phone desc)
-- 此时age就按升序排列,phone就按倒序排列
如果此时在执行如下语句,就不会有age就按升序排列,phone就按倒序排列
#根据age,phone进行降序一个升序,一个降序
explain select id,age,phone from tb_user order by age asc,phone desc;
#创建索引
create index idx_user_age_phone_ad on tb_user(age asc,phone desc);
#根据age,phone进行降序一个升序,一个降序
explain select id,age,phone from tb_user order by age asc,phone desc;
注意:上述的所有排序优化都有一个条件,就是覆盖索引,如果不是覆盖索引就不行
即order by优化主要就以下几点:
filesort
,大数据量排序时,可以适当增大排序缓冲区大小sort_buffer_size(默认为256k)
#删除掉目前的联合索引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,age字段分组
explain select profession,count(*) from tb_user group by profession,age;
查看当前sql语句的执行:
可见使用为Extra:Using temporary
再次创建索引,查看执行计划
create index idx_user_pro_age_sta on tb_user(profession,age,status);
limit一个常见的问题就是,在大数据量的情况下,越往后查询数据,limit的效率月底,例如limit 2000000,10,此时需要mysql排序前2000010记录,但是仅仅返回2000000和2000010之间的记录,其他的记录丢弃,查询排序的代价非常大
官方给出的优化方式是,通过覆盖索引和子查询的方式优化
-- 直接使用limit查询,效率很低,大概要19秒多
select *from tb_user_s2 limit 9000000,10;
-- 使用覆盖索引和子查询的方式优化
-- 先在子查询中找到对应的主键,然后再使用主键进行查询数据
-- 但是这种方式好像在mysql8.0.26语法不支持
select * from tb_user_s2 where id in(select id from tb_user_s2 order by in limit 9000000,10);
-- 那么可以使用另外的语法实现这个效果
-- 把select id from tb_user_s2 order by in limit 9000000,1
-- 返回的结果看成一张表,然后使用多表查询
select s.* from tb_user_s2 s, (select id from tb_user_s2 order by in limit 9000000,10) a where a.id=s.id;
-- 这样写大概查询是10秒,提高9秒的效率
当数据量很大时,count一次也是很耗时的
explain select count(*) from tb_user;
这个实际上是取决于InnoDB的处理方式
MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行count(*)的时候会直接返回这个数,效率很高
这里MyISAM的前提条件是没有where条件,如果有where条件,那么MyISAM也会比较慢
InnoDB引擎就麻烦,它执行count(*)的时候,需要把数据一行一行地从引擎里面读出来,然后累加计数
对于count来说,优化策略:就是自己计数,在插入或删除数据时自己记录下来
count是一个聚合函数,对于返回的结果集,一行行的判断,如果count函数的参数不是NULL,累计值就加1,否则不加,最后返回累计值,即count参数会统计数据的数量
count的几种用法:
count(*)
count(主键)
count(字段)
count(1)
-- count(*)就是求取某个表中数据的总体数量
mysql> select count(*)from tb_user_s1;
+----------+
| count(*) |
+----------+
| 16 |
+----------+
1 row in set (0.00 sec)
-- count(字段),会统计字段的数量,对字段里的值统计前会先判断是不是null,
-- 不是null才会统计进去,是null就不会统计
-- 更新某个id的profession为null
mysql> select count(profession)from tb_user_s1;
+----------+
| count(*) |
+----------+
| 15 |
+----------+
1 row in set (0.00 sec)
-- count(1)的使用和count(*)是一样的,也是会统计数据的总体数量
count(主键):
count(字段):
count(1):
count(*):
需要规避的一个问题是:在更新字段时需要根据带有索引的字段进行更新,即where后的条件字段尽量是带有索引的,否则,就会出现行锁升级为表锁,锁住整张表,从而使并发性能降低
InnoDB的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则会从行锁升级为表锁
视图(View1)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询使用的表,并且是在使用视图时动态生成的
通俗的讲,视图只保存了查询SQL逻辑,不保存查询结果.所以我们在创建视图的时候,主要的工作就落在创建这条SQL查询语句上
CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句 [WITH[CASCADED | LOCAL] CHECK OPTION]
例子:
create table student(
id int comment'编号',
name varchar(10) comment '姓名',
no varchar(11) comment '电话'
)comment '学生表';
insert into student values(1,'麻瓜',2000100100),(2,'愣头青',2000100102),(3,'铁头娃',2000100103),(4,'小老板',2000100100)
-- 创建视图
create or replace view stu_v_l as select id,name from student where id<=10;
查看创建视图语句:SHOW CREATE VIEW 视图名称;
查看视图数据:SELECT * FROM 视图名称……;
方式一:CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句[WITH[CASSCADED|LOCAL]CHECK OPTION]
方式二:ALTER VIEW 视图名称[(列名列表)] AS SELECT语句 [WITH [ CASCADED|LOCAL] CHECK OPTION]
DROP VIEW [IF EXISTS] 视图名称 [视图名称] ...
注意:视图在数据库中并不存在,虽然可以往视图里添加数据,但是实际上数据是被添加到视图所对应的基表中去了,并且视图只会显示符合创建条件时的数据,超出条件范围的数据不会视图中,即在视图中找不到超出创建视图条件的数据
举例:
-- 创建视图
create or replace view stu_v_l as select id,name from student where id<=20 with local check option;
-- 往视图中插入数据实际上就是往视图所对应的基表中插入数据
-- 这里所对应的基表就是tb_user_s1
insert into name_v_1 values (17,'Tom');
insert into name_v_1 values (20,'Jack');
-- 上面的两条数据只有(17,'Tom');能在视图中显示出来,因为(20,'Jack');超出了
-- 创建视图的条件
上面的with cascaded check option就是视图中检查选项的应用
WITH CHECK OPTION
子句创建视图时,MySQL会通过视图检查正在更改的每个行,例如插入、更新、删除,以使其符合视图的定义。MySQL允许基于另一个视图创建视图,它还会检查视图中的规则以保持一致性。为了确定检查的范围,mysql提供了两个选项:CASCADED
和LOCAL
,默认值为CASCADED
在创建视图时使用with … check option
的话,mysql就会检查视图中正在改变的每一行的数据,如果有数据不满足创建视图时的条件,那么就会报错,不允许视图改变,这里默认值就是 cascaded
不加也可以
cascaded与local的区别:
-- cascaded
create or replace view stu_v_l as select id,name from student where id<=20;
insert into stu_v_l values(5,'Tom');
insert into stu_v_l values(25,'Tom');#没有加上with local check option,mysql就不会在检查增删改操作是否违背了这个工作【
create or replace view stu_v_2 as select id,name from stu_v_l where id>=10 with local check option;
insert into stu_v_2 values(7,'Tom'); -- 报错
insert into stu_v_2 values(26,'Tom'); -- 也报错
insert into stu_v_2 values(15,'Tom'); -- 成功
create or replace view stu_v_3 as select id,name from stu_v_2 where id<=15;
insert into stu_v_3 values(11,'Tom'); -- 成功
insert into stu_v_3 values(17,'Tom'); -- 也成功
insert into stu_v_3 values(28,'Tom'); -- 报错
-- local
create or replace view stu_v_4 as select id,name from student where id<=15;
insert into stu_v_4 values(5,'Tom'); -- 成功
insert into stu_v_4 values(16,'Tom'); -- 也成功
create or replace view stu_v_5 as select id,name from stu_v_4 where id>=10 with local check option;
insert into stu_v_5 values(13,'Tom'); -- 成功
insert into stu_v_5 values(17,'Tom'); -- 也成功,首先v5中id>=10通过,又有with local check option关联到v4,虽然v4id条件<=15但是没有检查选项故也通过
insert into stu_v_5 values(9,'Tom'); -- 失败
create or replace view stu_v_6 as select id,name from stu_v_5 where id< 20;
insert into stu_v_6 values(14,'Tom'); -- 也成功
要使视图可更新,视图中的行与基础表(基表)中的行之间必须存在一对一的关系,如果视图包含以下任何一项,则该视图不可更新
比如:
在视图创建时里面包含一些
举例:
-- 创建视图,使用聚合函数
create view stu_v_count as select count(*) from student;
insert into stu_v_count values(10); -- 报错
操作简单
视图可以简化用户对数据的理解和操作,那些被经常使用查询的表就可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件
数据安全
数据库可以授权,但是最低也只 能授权到表上,不能授权到数据库中特定行和特定列上,但是通过视图用户只能查询和修改他们所能见到的数据
比如,我只想让某一个用户看到并修改学生表的id和name字段,那么就可以使用视图来使这些用户只看到id和name
数据独立
视图可以帮助用户屏蔽真实表结构的变化带来的影响
-- 1.
create view tb_user as select as select id,name,profession,age,gender,status,createtime from tb_user;
select * from tb_user_view;
-- 2.
create view tb_stu_course_view as select s.name student_name ,s.no student_no,c.name course_name from students, student_course sc,course c where s.id = student.id
存储过程是事先经过编译并存储在数据库中的一段SQL语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。
存储过程思想上很简单,就是数据库SQL语言层面的代码封装与重用。
其特点:
CREATE PROCEDURE 存储过程名称([参数列表])
BEGIN
--SQL语句
END;
CALL 名称([参数]);
SELECT*FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'xxx'; -- 查询指库的存储过程及状态信息
SHOWEATE PROCEDURE 储存过程名称; -- 查询某个存储过程的定义
DROP PROCEDURE [IF EXISTS] 存储过程
但是在finalshell当中创建存储过程时,语句执行的时候却报错了
该问题就出现在了分号当中,在命令行里,如果说要去执行存储过程创建的这个circle,就需要delimiter
来自定义指定sql语句的执行
注意:在命令行中,执行创建存储过程的SQL时,需要通过关键字
delimiter
指定SQL语句的结束符。
例如:
delilmiter $$
设置必须以$$
结尾才能结束
系统变量是MySQL服务器提供,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL)、会话变量(SESSION)
会话是什么?
指的是开启多个同类的窗口会话,一个会话里的变量不会影响另一个会话窗口
show [session|global] variables; --查看所有系统变量
show [session|global] variables like'...'; --可以通过like模糊匹配方式查找变量
select @@[session|global] 系统变量名; --查看指定变量的值
set [session|global] 系统变量名=值;
set @@[session|global] 系统变量名=值;
注意:
如果没有指定
SESSION/GLOBAL
,默认是SESSION
,会话变量。
mysql服务重新启动之后,所设置的全局参数会失效,要想不失效,可以在/etc/my.cnf
中配置。
用户定义变量是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用"@变量名" 使用就可以。其作用域为当前连接
set @var_name = expr [,@var_name = expr]...;
set @var_name = expr [,@var_name := expr]...;
select @var_naem := expr [,@var_naem:=expr]...;
select 字段名 INTO @var_name FROM 表名
select @var_naem;
注意:用户定义的变量无需对其进行声明或初始化,只不过获取到的值为NULL
局部变量是根据定义的在局部生效的变量,访问之前,需要DECLARE
声明。可用作存储过程变量和输入参数,局部变量的范围是在其内声明的BEGIN...END
块
declare 变量名 变量类型[default...];
变量类型是数据库字段类型:int,bigint,char,varchar,date,time等
set 变量名=值;
set 变量名:=值;
select 字段名 into 变量名 from 表名...;
if 条件1 then
....
elseif 条件2 then -- 可选
....
else -- 可选
....
end if;
案例:
create procedure p3()
begin
declare score int default 58;
declare result varchar(10);
if score >=85 then
set result := '优秀';
elseif score>=60 then
set result := '及格';
else
set result := '不及格';
end if;
select result;
end;
call p3();
类型 | 含义 | 备注 |
---|---|---|
IN | 该类参数作为输入,也就是需要调用时传入值 | 默认 |
OUT | 该类参数作为输出,也就是该参数可以作为返回值 | |
INOUT | 即可以作为输入参数,也可以作为输出参数 |
用法:
create procedure 储存过程名称([IN/OUT/INOUT 参数名 参数类型])
begin
-- SQL语句
END;
create procedure p4(in score int,out result varchar(10))
begin
if score >=85 then
set result := '优秀';
elseif score>=60 then
set result := '及格';
else
set result := '不及格';
end if;
select result;
end;
call p3(68,@result);
create procedure p5(inout score double)
begin
set score := score * 0.5;
end;
set @score = 198;
call p5(@score);
select @score;
CASE case_value
WHEN when_value1 then statement_list1
[WHEN when_value2 then statement_list2]...
END CASE;
CASE case_value
WHEN search_condition1 then statement_list1
[WHEN search_condition2 then statement_list2]...
END CASE;
create procedure p6(IN month int)
begin
declare result varchar(10);
case
when month >= 1 and month <=3 then
set result := '第一季度';
when month >= 4 and month <=6 then
set result := '第二季度';
when month >= 7 and month <=9 then
set result := '第三季度';
else
set result := '非法参数';
end case;
select concat('你输入的月份为:',month,',所属的季度为:',result);
end;
call p6(4);
while循环是有条件的循环控制语句。满足条件时候,再执行循环体中的SQL语句。具体语法为:
#先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑
while 条件 DO
SQL逻辑
end while;
-- A. 定义局部变量,记录累加之后的值;
-- B. 每次循环一次,就会对n进行减1,如果n减到0,则退出循环
create procedure p7(in n int)
begin
declare total int default 0;
while n>0 do
set total := total +n;
set n := n-1;
end while;
select total;
end;
call p7(10);
repeat是有条件的循环控制语句,当满足条件的时候退出循环。具体语法为:
#先执行一次逻辑,然后判定逻辑是否满足,如果满足,则退出。如果不满足,则继续下一次循环
repeat
SQL逻辑...
until 条件
end repeat;
-- A. 定义局部变量,记录累加之后的值;
-- B. 每次循环一次,就会对n进行减1,如果n减到0,则退出循环
create procedure p8(in n int)
begin
declare total int default 0;
repeat
set total := total+n;
set n := n-1;
until n<=0
end repeat;
select total;
end;
call p8(10);
loop实现简单的循环,如果不在SQL逻辑中怎加退出循环的条件,可以用其来实现简单的死循环。loop可以配合一下两个语句使用:
[begin_label:]loop
SQL逻辑...
end loop [end_label];
leave label; -- 退出指定标记的循环体
iterate label; -- 直接进入下一次循环
create procedure p9(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;
select total;
end;
call p9(100);
2、
-- loop计算从1到n之问的偶数累加的值,n为传入的参数值。
-- A.定文局部变量,记录累加之后的值;
-- B.每循环一次,就会对n进行-1,如果n减到8,则退出循环―---> leave xx
-- C.如果当次累加的数据是奇数,则直接进入下一次循环. --------> iterate xx
create procedure p10(in n int)
begin
declare total int default 0;
sum:loop
if n<=0 then
leave sum;
end if;
if n%2 = 1 then
set n:= n-1;
iterate sum;
end if;
set total := total + n;
set n := n -1;
end loop;
select total;
end;
call p10(10);
游标(CURSOR) 是用来存储查询结果集的数据类型,在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游的声明、OPEN
、FETCH
和CLOSE
,其语法分别如下:
declare 游标名称 cursor for 查询语句;
open 游标名称;
fetch 游标名称 into 变量[,变量];
close 游标名称;
create procedure p11(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;
# 循环报错是因为while true会一直循环下去,在循环的过程当中,每循环一次就需要从游标当中去获取下一条的记录
-- 在吧这个记录赋值uname和upro,当循环到最后一条之后再进行下一次循环时,游标里没有数据了,所以此时再去获取游标当中的数据就会报错
close u_cursor;
end;
call p11(40);
条件处理程序(handler)可以用来定义再流程控制结构执行过程中遇到时相应的处理步骤。具体语法为:
declare handler_action handler for condition_value [.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代码的简写
对于上例while循环报错问题进行解决:
create procedure p11(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;
declare exit handler for SQLSTATE '02000' close u_cursor; # not found等替换
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;
# 循环报错是因为while true会一直循环下去,在循环的过程当中,每循环一次就需要从游标当中去获取下一条的记录
-- 在吧这个记录赋值uname和upro,当循环到最后一条之后再进行下一次循环时,游标里没有数据了,所以此时再去获取游标当中的数据就会报错
close u_cursor;
end;
call p11(40);
M有SQL官方文档状态栏含义
存储函数是有返回值的存储过程,存储函数的参数只能是IN类型的。具体语法如下:
create function 存储函数名称({参数列表})
returns type [characteristic...]
begin
--SQL语句
return...;
end;
characteristic说明:
-- deterministic:相同的输入参数总是产生相同的结果
-- no sql:不含sql语句
-- reads sql data:包含读取数据的语句,但不包含写入数据的语句
create function fun1(n int)
returns int deterministic -- deterministic用于解决指定DETERMINISTIC, NO SQL, or READS SQL DATA的特性报错
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(50);
触发器是与表有关的数据库对象,指再insert/update/delete之前或之后,触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用再数据库端确保数据的完整性,日志记录,数据校验等操作
使用别名OLD和NEW来引用触发器中发生的记录内容,这与其他的数据库是相似的。现在触发器还只支持行级触发,不支持语句触发
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,默认为当前数据库
create table user_logs(
id int(11) not null auto_increment primary key ,
operation varchar(20) not null comment '操作类型,insert/update/delete',
operate_time datetime not null comment '操作时间',
operate_id int not null comment '操作ID',
operate_params varchar(500) comment '操作参数'
)engine=innodb default charset = utf8;
create trigger tb_user_insert_trigger
after insert on tb_user for each row
begin
insert into user_logs(id,operation,operate_time,operate_id,operate_params)values
(null,'insert',now(),new.id,concat('插入的数据内容为:id=',new.id,',name=',new.name,'phone=',new.phone,'email=',new.email));
end;
-- 查看
show triggers ;
-- 删除
drop trigger tb_user_insert_trigger;
-- 插入数据到tb_user
insert into tb_user(id,name,phone,email)values (25,'二皇子','123456789','[email protected]')
create trigger user_logs_trigger2
after update on student for each row
begin
insert into user_logs(id,operation,operate_time,operate_id,operate_params) values
(null,'insert',now(),NEW.id,
concat('更新之前数据为:id=',old.id,'name=',old.name,'num=',old.num
,'更新之后的数据为:id=',new.id,'name=',new.name,'num=',new.num));
end;
show triggers;
create trigger user_logs_delete_trigger
after update on student for each row
begin
insert into user_logs(id,operation,operate_time,operate_id,operate_params) values
(null,'delete',now(),OLD.id,
concat('删除之前的数据为:id=',old.id,'name=',old.name,'num=',old.num));
end;
M)SQL5.5版本开始,默认使用InoDB存储引擎,它擅长事务处理,具有崩溃恢复特性,在日常开发中使用非常广泛。下面是InnoDB架构图,左侧为内存结构,右侧为磁盘结构。
假如说没有缓冲区,意味着我们执行增删改查的时候,每一次数据库都要区操作磁盘空间,那么就会存在大量的磁盘io,而且在业务比较复杂的系统当中会产生大量的随机磁盘io,所以此时是非常耗性能的
因此有了缓冲区之后,就不用每一次都去操作磁盘文件了。可以直接操作缓冲区,然后每隔断时间或者说触发了某一种机制之后,再将缓冲区当中的数据再刷到磁盘当中就可以了
Buffer Pool:缓冲池
Change Buffer:更矮缓冲区
内存结构中的数据是怎么刷新到磁盘当中的呢?
后台线程其作用:将InnoDB存储引擎的缓冲池当中的数据,在合适的时机刷新到磁盘文件当中
Master Thread
核心后台线程,负责调度其他线程,还负责将缓冲池中的数据异步刷新到磁盘中,保持数据的一致性,还包括脏页的刷新、合并插入缓存、undo页的回收
IO Thread
在InnoDB存储引擎中大量使用了AIO来处理IO请求,这样可以极大地提高数据库的性能,而IOThread主要负责这些lO请求的回调
线程类型 | 默认个数 | 职责 |
---|---|---|
Read thread | 4 | 负责读操作 |
Write thread | 4 | 负责写操作 |
Log thread | 1 | 负责将日志缓冲区刷新到磁盘 |
Insert buffer thread | 1 | 负责将写缓冲区内容刷新到磁盘 |
show engine innodb status;
Purge Thread
主要用于回收事务已经提交了的undo log,在事务提交之后,undo log可能不用了,就用它来回收
Page Cleaner Thread
协助Master Thread刷新脏页到磁盘的线程,它可以减轻Master Thread 的工作压力,减少阻塞
- 对InnoDB引擎的整个体系结构,当我们业务在操作的时候,那么会直接区操作这一块的缓冲区,如果缓冲区当中没有数据会将磁盘当中的数据加载回来,然后再存储再缓冲区当中。
- 我们再增删改查的时候,都会去操作这一块的缓冲区,缓冲区当中的数据会以一定的频率或者说一定的时机,要通过这组后台线程刷新到磁盘当中,然后在磁盘当中进行永久化地保留下来
●特性
重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。
该日志文件由两部分组成:重做日志缓冲(redo log buffer
)以及重做日志文件(redo log file
) ,前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改傣息都存到该日志文件中,用于在刷新脏页到磁盘,发生错误时,进行数据恢复使用。
回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚和MVCC(多版本并发控制)。
undo log和redo log记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。
当前读:
读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记 录进行加锁。对于我们日常的操作,如:select...lock in share mode(共享锁)
,select... for update
、update
、insert
、delete(排他锁)
都是一种当前读。
当事务提交了也查不到,是因为当前隔离级别是repeatable read,保证了可重复读,所以使用当前读读到最新数据
快照读:
简单的select (不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。
Read Committed:每次select,都生成一个快照读。
Repeatable Read:开启事务后第一个select语句才是快照读的地方。
Serializable:快照读会退化为当前读。
MVCC:
全称 Muti- version Concurrency Control
,多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,快照读为MySQL实现MCC提供了一个非阻塞读功能。MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log日志、readView.
隐藏字段 | 含义 |
---|---|
DB_TRX_ID | 最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID |
DB_ROLL_PTR | 回滚指针,指向这条记录的上一个版本,用于配合undo log,指向上一个版本 |
DB_ROW_ID | 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段 |
回滚日志,在insert、update、delete的时候产生的便于数据回滚的日志。
当insert的时候,产生的undo log日志只在回滚时需要,在事务提交后,可被立即删除。
而update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会立即被删除。
不同事务或相同事务对同一条记录进行修改,会导致该记录的undo log日志生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录
那么当我们进行查询的时候,到底应该返回哪一个版本呢?由 ReadView组件来控制
ReadView(读视图)是快照读SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务〈未提交的) id。ReadView中包含了四个核心字段:
字段 | 含义 |
---|---|
m_ids | 当前活跃的事务ID集合 |
min_trx_id | 最小活跃事务ID |
max_trx_id | 预分配事务ID,当前最大事务ID+1(因为事务ID是自增的) |
creator_trx_id | ReadView创建者的事务ID |
不同的隔离级别,生成ReadView的时机不同:
read committed:在事务中每一次指向快照读时生成readview
repeatable read:仅在事务中第一次指向快照读时生成readview,后续复用该readview
RC隔离级别下,在事务中每一次指向快照时生成readview
RR隔离级别下,仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView
Mysql数据库安装完成后,自带了一下四个数据库,具体作用如下:
数据库 | 含义 |
---|---|
mysql | 存储MySQL服务器正常运行所需要的各种信息(时区、主从、用户、权限等) |
information_schema | 提供了访问数据库元数据的各种表和视图、包含数据库、表、字段类型及访问权限等 |
performance_schema | 为MysQL服务器运行时状态提供了一个底层监控功能,主要用于收集数据库服务器性能参数 |
sys | 包含了一系列方便DBA和开发人员利用performance_schema性能数据库进行性能调优和诊断的视图 |
该mysql不是指mysql服务,而是指mysql的客户端工具
语法:
mysql [options] [dataabase]
选项:
-u, --uesr=name #指定用户名
-p, --password[=name] #指定密码
-h, --host=name #指定服务器IP或域名
-p. --port=port #指定连接端口
-e, --execute=name #指向SQL语句并退出
-e选项可以在msql客户端指向SQL语句,而不用连接到mysql数据库再执行,对于一些批处理脚本,这种方式尤其方便
示例:
mysql -uroot-p123455 db01 -e "select*from stu";
mysqladmin是一个执行管理操作的客户端程序。可以用它来检查服务器的配置和当前状态、创建并删除数据库等。
例如:
由于服务器生成的二进制日志文件以二进制格式保存,所以如果想要检查这些文本的文本格式,就会使用到mysqlbinlog日志管理工具。
语法:
mysqlbinlog [options] log-files1 log-files2 ...
选项:
-d, --database=name 指定数据库名称,只列出指定的数据库相关操作。
-O, --offset=# 忽略掉日志中的前n行命令。
-r,--result-file=name 将输出的文本格式日志输出到指定文件。
-s, --short-form 显示简单格式,省略掉一些信息。
--start-datatime=date1 --stop-datetime=date2 指定日期间隔内的所有日志。
--start-position=pos1 --stop-position=pos2 指定位置间隔内的所有日志。
mysqlshow客户端对象查找工具,用来很快地查找存在哪些数据库、数据库中的表、表中的列或者索引
语法:
mysqlshow [options] [db_name [table_name[col_name]]]
选项:
--count 显示数据库及表的统计信息(数据库,表均可以不指定)
-i 显示指定数据库或者指定表的状态信息
示例:
#查询每个数据库的表的数量及表中记录的数量
mysqlshow-uroot -p2143 --count
#查询test库中每个表中的字段书,及行数
mysqlshow -uroot-p2143 test --count
#查询test库中book表的详细情况
mysqlshow -uroot -p2143 test book --count
mysqldump客户端工具用求备份数据库或在不同数据库之间进行数据迁移。备份内容包含创建表,及插入表的SQL语句。
语法:
mysqldump [options] db_name [tables]
mysqldump [options] --databasel-B db1[db2 db3...]
mysqldump [options] --all-databases/-A
连接选项:
-U, --USer=name 指定用户名
-p,--password[=name] 指定密码
-h, --host=name 指定服务器ip或域名
-P, --port一# 指定连接端口
输出选项:
--add-drop-database 在每个数据库创建语句前加上drop database语句
--add-drop-table 在每个表创建语句前加上 drop table语句,默认开启;不开启(--skip-add-drop-table)
-n, --no-create-db 不包含数据库的创建语句
-t, --no-create-info 不包含数据表的创建语句
-d --no-data 不包含数据
-T, --tab=name 自动生成两个文件:一个.sql文件,创建表结构的语句;一个.txt文件,数据文件
案例:
mysql信任的文件存放目录: /var/lib/mysql-files/
所以在备份时,应该备份在这个目录下
mysqlimport是客户端数据导入工具,用来导入mysqldump加-T参数后导出的文本文件
语法:
mysqlimport [options] db_name textfile [textfile2...]
示例:
mysqlimport -uroot -p1234 test /tmp/city.txt
如果需要导入sql文件,可以使用mysql中的source指令:
语法:
source/root/xxxxx.sql