前言
以下是我整理了关于MySQL数据库的常用命令和使用场景,一些基本的概念,比如索引、约束和事务,以及常见的问题和解决方案,欢迎大家浏览并留言,若有错误的地方请大家指正。
MySQL函数
流程控制函数
①if(expr,v1,v2)函数
实现if-else的效果,如果expr是true,返回v1。如果expr是false,返回v2。
SELECT score,if(score<60,'不及格','合格') AS 备注 FROM tb_student limit 10;
②ifnull()函数
IFNULL(expressiON, alt_value)
IFNULL() 函数用于判断第一个表达式是否为 NULL,如果为 NULL 则返回第二个参数的值,如果不为 NULL 则返回第一个参数的值。
参数说明:
expressiON 必须,要测试的值
alt_value 必须,expressiON 表达式为 NULL 时返回的值
第一个参数为 NULL:
SELECT IFNULL(NULL, “RUNOOB”);
以上实例输出结果为:
RUNOOB
第一个参数不为 NULL:
SELECT IFNULL(“Hello”, “RUNOOB”);
以上实例输出结果为:
Hello
SELECT ifnull(NULL,1),ifnull(1,2);
③CASE…WHEN函数的三种用法
1, 等值判断:可以实现多条件的查询值筛选
-- 语法
CASE 要判断的字段或表达式
WHEN 常量1 THEN 要显示的值1或语句1
WHEN 常量2 THEN 要显示的值2或语句2
...
ELSE 要显示的值n或语句n
END
2, 区间判断:类似于if-else if-else的效果
-- 语法
CASE
WHEN 条件1 THEN 要显示的值1或语句1
WHEN 条件2 THEN 要显示的值2或语句2
...
ELSE 要显示的值n或语句n
END
3, CASE … WHEN和聚合函数联用
SELECT sc.c, cname,
max(score) 最高分,min(score) 最低分,avg(score) 平均分,
sum(CASE WHEN score>60 THEN 1 else 0 END)/count(*) 及格率,
sum(CASE WHEN score>=70 and score<80 THEN 1 else0 END)/count(*) 中等率,
sum(CASE WHEN score>=80 and score<90 THEN 1 else0 END)/count(*) 优良率,
sum(CASE WHEN score>=90 THEN 1 else 0 END)/count(*) 优秀率
FROM sc LEFT JOIN course
ON sc.C = course.C
group by sc.c;
窗口函数(排序函数)
窗口函数
窗口函数,也叫OLAP函数(Online Anallytical Processing,联机分析处理),可以对数据库数据进行实时分析处理。
窗口函数的基本语法如下:
<窗口函数> over (partition by <用于分组的列名>
order by <用于排序的列名>)
<窗口函数> 的位置,可以放以下两种函数:
1) 专用窗口函数,包括后面要讲到的rank, dense_rank, row_number等专用窗口函数。
2) 聚合函数,如sum. avg, count, max, min等。
partition by用来对表分组。order by子句的功能是对分组后的结果进行排序。
因为窗口函数是对where或者group by子句处理后的结果进行操作,所以窗口函数原则上只能写在select子句中。
-- 排序,使用窗口函数
SELECT name,price
RANK() over (ORDER BY price DESC) AS rank_1,
DENSE_RANK() over (ORDER BY price DESC) AS rank_2
from Products;
注意:MySQL8.0以后的版本才支持窗口函数
排序函数
三种排序函数,分别是rank()、dense_rank()和row_number()函数,简单说一下它们的区别:
函数 | 含义 | 例子 |
---|---|---|
rank() | 考虑数据的重复性,挤占坑位 | 1,2,2,4 |
dense_rank() | 考虑数据的重复性,不挤占坑位 | 1,2,2,3 |
row_number() | 不考虑数据的重复性,按照顺序依次标上顺序 | 1,2,3,4 |
1) RANK()函数
rank() over
跳跃排序: 在每个分组内,如果有两个第一位时,接下来就是第三位
作用:查出指定条件后的进行排名,条件相同排名相同,排名间断不连续。
说明:例如学生排名,使用这个函数,成绩相同的两名是并列,下一位同学空出所占的名次。即:1134557
语法如下:
rank() over(partition by 分组字段 order by 排序字段 desc) as '序号命名'
RANK() OVER([query_partition_clause] order_by_clause)
query_partition_clause:分组
order_by_clause:排序
+------------+----------+--------+--------+
| Department | Employee | Salary | Rank |
+------------+----------+--------+--------+
| IT | Jim | 90000 | 1 |
| IT | Max | 90000 | 1 |
| IT | li | 80000 | 3 |
| Sales | Henry | 80000 | 1 |
| Sales | pan | 95000 | 2 |
+------------+----------+--------+--------+
2) DENSE_RANK()函数
dense_rank() over
连续排序,在每个分组内,如果有两个第一级时,接下来仍然是第二级。
作用:查出指定条件后的进行排名,条件相同排名相同,排名间断不连续。
说明:和rank() over 的作用相同,区别在于dense_rank() over 排名是密集连续的。例如学生排名,使用这个函数,成绩相同的两名是并列,下一位同学接着下一个名次。即:1 1 2 3 4 5 5 6
语法如下:
dense_rank() over(partition by 分组字段 order by 排序字段 desc) as '序号命名'
DENSE_RANK() OVER([query_partition_clause] order_by_clause)
其用法类似于RANK()函数,所以在此就用一个例子来演示这两个函数的用法:
select Score ,dense_rank() over( order by S.Score desc) as "Rank" from Scores S
实现的是分数排名,分数相同的排名相同,下一个名次是一个连续的整数值。
+------------+----------+--------+--------+
| Department | Employee | Salary | Rank |
+------------+----------+--------+--------+
| IT | Jim | 90000 | 1 |
| IT | Max | 90000 | 1 |
| IT | li | 80000 | 2 |
| Sales | Henry | 80000 | 1 |
| Sales | pan | 95000 | 2 |
+------------+----------+--------+--------+
3) ROW_NUMBER()函数
row_number() over
去重排序: 在每个分组内,为查询出来的每一行记录生成一个序号,依次排序且不会重复(即使结果相同,也会排出 1 2 3 名)
作用:查出指定条件后的进行排名,条件相同排名也不相同,排名间断不连续。
说明:这个函数不需要考虑是否并列,即使根据条件查询出来的数值相同也会进行连续排序。即:1 2 3 4 5 6
语法如下:
row_number() over(partition by 分组字段 order by 排序字段 desc) as '序号命名'
ROW_NUMBER() OVER([query_partition_clause] order_by_clause)
用法和上面两个类类似,在此就不再赘述了。
+------------+----------+--------+--------+
| Department | Employee | Salary | 编号 |
+------------+----------+--------+--------+
| IT | Jim | 90000 | 1 |
| IT | Max | 90000 | 2 |
| IT | li | 90000 | 3 |
| Sales | Henry | 80000 | 1 |
| Sales | pan | 95000 | 2 |
+------------+----------+--------+--------+
使用提示
-
dense_rank() over 后面跟排序的依据的列,下面是用了一个排序好的列(order by 列名 desc)。
-
如果select中有一列是用rank()这类函数,其它的列都会按着它这列规定好的顺序排。
-
使用row_number() over() 函数时,over()里的排序晚于where 、group by、order by的执行
-
partiton by 用于给结果集分组,如果未指定则将整个结果集作为一个分组
-
partiton by 与聚合函数区别:可以返回一个分组中多条记录,而聚合函数一般只有一个反应统计值的记录
其他函数
①FORMAT(x,y)函数
把x格式化为以逗号隔开的数字序列,y是结果的小数位数
SELECT format(1235.23564,2);
结果 => 1.235.24
1.FORMAT函数在mysql中是数据内容格式化的,格式化后得到结果:###,###,#####
SELECT format(100000,2);
输出结果:
100,000.00
2.可以格式化数据为整数或者浮点数
select format(100.31111,2);
输出结果:
100.31
select format(100.31111,0);
输出结果:
100
3.具有四舍五入的功能
SELECT FORMAT(423423234.65534453,2);
输出结果:
423,423,234.66
②INET_ATON(ip)函数
返回IP地址的数字表示
SELECT inet_atON('10.122.89.47');
结果 => 175790383
③INET_NTOA(NUM)函数
返回数字所代表的IP地址
SELECT atON_inet(175790383);
结果 => 10.122.89.47
④password(str)函数
密码加密函数,str为NULL,返回NULL。
password在MySQL服务器鉴定系统中使用。不应该用在个人的应用程序中。
加密是单向的(不可逆),加密后的密码保存到用户权限表中。
执行密码加密与UNIX中密码加密方式不同
⑤md5(str)函数
加密函数;
参数为字符串,该函数为字符串算出一个MD5 128比特校验和
返回值以32位16进制数字的二进制字符串形式返回
str为NULL,返回NULL
SELECT md5('mypwd');
⑥encode(str,pswd_str)、decode(加密的字符串,pswd_str)函数
加密:encode(被加密的密码,密码);
解密:decode(encode(被加密的密码,密码),密码); //也可以用上面返回的二进制字符串
⑦union/union all 合并查询出来的数据
如果需要将两条查询语句的结果作为一个整体显示出来,就需要用到 union 或者 union all 关键字。union (或称为联合)的作用是将多个结果合并在一起显示出来。
什么时候需要使用组合查询:
- 在单个查询中从不同的表返回类似结构的数据;
- 对单个表执行多个查询,按单个查询返回数据。
UNION
和UNION ALL
二者区别:
-
UNION
会自动压缩多个结果集合中的重复结果,类似去重效果。 -
UNION ALL
则将所有的结果全部显示出来,不管是不是重复。 -
如果确实需要每个条件的匹配行全部出现(包括重复行),则必须使用
UNION ALL
而不是WHERE
如何使用UNION
-- 原语句
SELECT vEND_id,prod_id,prod_price FROM products WHERE prod_price <= 5;
SELECT vEND_id,prod_id,prod_price FROM products WHERE vEND_id IN (1001,1002);
-- 使用WHERE写法
SELECT vEND_id,prod_id,prod_price FROM products
WHERE prod_price <= 5 OR vEND_id IN (1001,1002);
-- 使用UNION拼接
SELECT vEND_id,prod_id,prod_price FROM products WHERE prod_price <= 5
UNION
SELECT vEND_id,prod_id,prod_price FROM products WHERE vEND_id IN (1001,1002);
UNION指示MySQL执行两条SELECT语句,并把输出组合成单个查询结果集。
UNION规则
-
UNION必须由两条以上的SELECT语句组成,语句之间用关键字UNION分割。
-
UNION中的每个查询必须包含相同的列、表达式或聚集函数(各个列不需要以相同的次序列出)。
-
列数据类型必须兼容:类型不必完全相同,但必须是DBMS可以隐含地转换的类型。
-
如果取出来的数据不需要去重,使用UNION ALL。
对组合查询结果排序
SELECT语句的输出用ORDER BY子句排序。在用UNION组合查询时,只能使用一条ORDER BY子句,它必须出现在最后一条SELECT语句之后。对于结果集,不存在用一种方式排序一部分,而又用另一种方式排序另一部分的情况,因此不允许使用多条ORDER BY子句。
SELECT vEND_id,prod_id,prod_price FROM products WHERE prod_price <= 5
UNION
SELECT vEND_id,prod_id,prod_price FROM products WHERE vEND_id IN (1001,1002)
ORDER BY vEND_id,prod_price;
UNION ALL使用场景
- sql 中的组合in,可用 union all 来代替,提高查询效率
修改前:组合in sql
SELECT ***, ***, ***, ***, ***
FROM e_rating_info
WHERE rating_quantity <> 0 AND (***, ***)
IN (('***', '***'), ('***', '***'),
('***', '***'), ('***', '***'),
('***', '***'), ('***', '***'),
('***', '***'), ('***', '***'),
('***', '***'), ('***', '***'))
ORDER BY *** DESC
修改后:UNION ALL sql
<select id="queryRatingInfo" resultType="***">
<foreach collection="ratingList" item="item" index="index" open="" separator="UNION ALL" close="">
SELECT ***, ***, ***, ***, ***
FROM e_rating_info
WHERE rating_quantity <> 0
AND country_code = #{item.***}
AND asin = #{item.***}
</foreach>
ORDER BY *** DESC;
</select>
注意:如果系统中进行了分表,一定要保证各个表的字段顺序一致。特别是修改的时候。否则,如果使用汇总查询结果,可能会出现问题。
注意事项
-
UNION 和 UNION ALL 内部的 SELECT 语句必须拥有相同数量的列。
-
每条 SELECT 语句中列的顺序必须相同。注意:按照字段顺序匹配,而不是按照字段名称匹配。
- union all 结果字段的顺序以 union all 前面的表字段顺序为准。union all 后面的表的数据会按照字段顺序依次附在后面,而不是按照字段名称匹配。
事务
事务是由一个单元的一个或多个SQL语句组成,这个单元的每个SQL语句是相互依赖的,而且单元作为一个整体是不可分割的。如果单元中的一个语句不能完成,整个单元就会回滚(撤销),所有影响到数据将返回到事务开始以前的状态,因此,只有事务中所有SQL语句执行成功,则才能说明这个事务被成功执行。
添加事务
-- 开启事务
BEGIN;
-- 转账操作
-- 1. 查询李四账户金额是否大于500
-- 2. 李四账户 -500
UPDATE account set mONey = mONey - 500 AND name = '李四';
出现异常了... -- 此处不是注释,在整体执行时会出问题,后面的sql则不执行
-- 3. 张三账户 +500
UPDATE account set mONey = mONey + 500 AND name = '张三';
-- 提交事务
COMMIT;
-- 回滚事务
ROLLBACK;
事务的四大特征
数据库事务特性:原子性(Atomic)、一致性(Consistency)、隔离性(Isolation)、持久性(Durabiliy)。简称ACID。
-
原子性:组成一个事务的多个数据库操作是一个不可分割的原子单元,只有所有操作都成功,整个事务才会提交。任何一个操作失败,已经执行的任何操作都必须撤销,让数据库返回初始状态。
-
一致性:事务操作成功后,数据库所处的状态和它的业务规则是一致的。即数据不会被破坏。如A转账100元给B,不管操作是否成功,A和B的账户总额是不变的。
-
隔离性:在并发数据操作时,不同的事务拥有各自的数据空间,它们的操作不会对彼此产生干扰。
-
持久性:一旦事务提交成功或回滚,事务中的所有操作都必须持久化到数据库中。
事务的隔离级别
READ-UNCOMMITTED(读取未提交):最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
READ-COMMITTED(读取已提交):允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。MySQL的默认隔离级别,MVCC多版本并发控制解决幻读。
SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
索引
官方介绍索引是帮助MySQL高效获取数据的数据结构。简单来讲,数据库索引就像是书前面的目录,能加快数据库的查询速度。用于帮助我们在大量数据中快速定位到我们想要查找的数据。
存储引擎
InnoDB
InnoDB是 MySQL 默认的事务型存储引擎,只有在需要它不支持的特性时,才考虑使用其它存储引擎。
实现了四个标准的隔离级别,默认级别是可重复读(REPEATABLE READ)。在可重复读隔离级别下,通过多版本并发控制(MVCC) + 间隙锁(Next-Key Locking)防止幻读。
主索引是聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升。
内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够加快读操作并且自动创建的自适应哈希索引、能够加速插入操作的插入缓冲区等。
支持真正的在线热备份。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。
MyISAM
设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用它。
提供了大量的特性,包括压缩表、空间数据索引等。
不支持事务。不支持行级锁,只能对整张表加锁,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但在表有读取操作的同时,也可以往表中插入新的记录,这被称为并发插入(CONCURRENT INSERT)。
可以手工或者自动执行检查和修复操作,但是和事务恢复以及崩溃恢复不同,可能导致一些数据丢失,而且修复操作是非常慢的。
如果指定了 DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏,需要执行修复操作。
二者比较
- 事务: InnoDB 是事务型的,可以使用 Commit 和 Rollback 语句。
- 并发: MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。
- 外键: InnoDB 支持外键。
- 备份: InnoDB 支持在线热备份。
- 崩溃恢复: MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。
- 其它特性: MyISAM 支持压缩表和空间数据索引。
B-Tree和B+Tree
B-Tree
是磁盘等外存储设备设计的一种平衡查找树。首先了解一下磁盘的相关知识。
系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。
InnoDB存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB存储引擎中默认每个页的大小为16KB,可通过参数innodb_page_size将页的大小设置为4K、8K、16K,在MySQL中可通过如下命令查看页的大小:
mysql> show variables like 'innodb_page_size';
而系统一个磁盘块的存储空间往往没有这么大,因此InnoDB每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小16KB。InnoDB在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘I/O次数,提高查询效率。
B-Tree结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述B-Tree,首先定义一条记录为一个二元组[key, data] ,key为记录的键值,对应表中的主键值,data为一行记录中除主键外的数据。对于不同的记录,key值互不相同。
一棵m阶的B-Tree有如下特性:
-
每个节点最多有m个孩子。
-
除了根节点和叶子节点外,其它每个节点至少有Ceil(m/2)个孩子。
-
若根节点不是叶子节点,则至少有2个孩子。
-
所有叶子节点都在同一层,且不包含其它关键字信息。
-
每个非终端节点包含n个关键字信息(P0,P1,…Pn, k1,…kn)。
-
关键字的个数n满足:ceil(m/2)-1 <= n <= m-1。
-
ki(i=1,…n)为关键字,且关键字升序排序。
-
Pi(i=1,…n)为指向子树根节点的指针。P(i-1)指向的子树的所有节点关键字均小于ki,但都大于k(i-1)。
B-Tree中的每个节点根据实际情况可以包含大量的关键字信息和分支,如下图所示为一个3阶的B-Tree:
每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为17和35,P1指针指向的子树的数据范围为小于17,P2指针指向的子树的数据范围为17~35,P3指针指向的子树的数据范围为大于35。
模拟查找关键字29的过程:
根据根节点找到磁盘块1,读入内存。【磁盘I/O操作第1次】
比较关键字29在区间(17,35),找到磁盘块1的指针P2。
根据P2指针找到磁盘块3,读入内存。【磁盘I/O操作第2次】
比较关键字29在区间(26,30),找到磁盘块3的指针P2。
根据P2指针找到磁盘块8,读入内存。【磁盘I/O操作第3次】
在磁盘块8中的关键字列表中找到关键字29。
分析上面过程,发现需要3次磁盘I/O操作,和3次内存查找操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。而3次磁盘I/O操作是影响整个B-Tree查找效率的决定因素。B-Tree相对于AVLTree缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率。
B+Tree
B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构,InnoDB存储引擎就是用B+Tree实现其索引结构。
从上面的B-Tree结构图中可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大增加每个节点存储的key值数量,降低B+Tree的高度。
B+Tree相对于B-Tree有几点不同:
非叶子节点只存储键值信息。
所有叶子节点之间都有一个链指针。
数据记录都存放在叶子节点中。
将上一节中的B-Tree优化,由于B+Tree的非叶子节点只存储键值信息,假设每个磁盘块能存储4个键值及指针信息,则变成B+Tree后其结构如下图所示:
通常在B+Tree上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。因此可以对B+Tree进行两种查找运算:一种是对于主键的范围查找和分页查找,另一种是从根节点开始,进行随机查找。
实际情况中每个节点可能不能填充满,因此在数据库中,B+Tree的高度一般都在2到4层。MySQL的InnoDB存储引擎在设计时是将根节点常驻内存的,也就是说查找某一键值的行记录时最多只需要1到3次磁盘I/O操作。
数据库中的B+Tree索引可以分为聚集索引(clustered index)和辅助索引(secondary index)。上面的B+Tree示例图在数据库中的实现即为聚集索引,聚集索引的B+Tree中的叶子节点存放的是整张表的行记录数据。辅助索引与聚集索引的区别在于辅助索引的叶子节点并不包含行记录的全部数据,而是存储相应行数据的聚集索引键,即主键。当通过辅助索引来查询数据时,InnoDB存储引擎会遍历辅助索引找到主键,然后再通过主键在聚集索引中找到完整的行记录数据。
索引的分类
-
主键索引
索引列中的值必须是唯一的,不允许有空值。 -
普通索引
MySQL中基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值。 -
唯一索引
索引列中的值必须是唯一的,但是允许为空值。 -
全文索引
-
MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。查找条件使用 MATCH AGAINST,而不是普通的 WHERE。
-
全文索引一般使用倒排索引实现,它记录着关键词到其所在文档的映射。
-
InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。
-
-
空间索引
MySQL在5.7之后的版本支持了空间索引,而且支持OpenGIS几何数据模型。MyISAM 存储引擎支持空间数据索引(R-Tree),可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。 -
前缀索引
在文本类型如CHAR,VARCHAR,TEXT类列上创建索引时,可以指定索引列的长度,但是数值类型不能指定。
其它(按照索引列数量分类)
- 单列索引
- 组合索引
组合索引的使用,需要遵循最左前缀匹配原则(最左匹配原则)。一般情况下在条件允许的情况下使用组合索引替代多个单列索引使用。
常用索引相关的语句
-- 查询表中所有的索引
show index FROM [表名];
-- 删除表中的指定索引
alter table [表名] drop index [索引名];
-- 创建组合索引
create index [组合索引名] ON [表名] ([索引名1], [索引名2]...);
建索引的原则
- 最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(
>
、<
、between
、like
)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4
如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。 =和in
可以乱序,比如a = 1 and b = 2 and c = 3
建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式。- 尽量选择区分度高的列作为索引,区分度的公式是
count(distinct col)/count(*)
,表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,一般需要JOIN的字段我们都要求是0.1以上,即平均1条扫描10条记录。 - 索引列不能参与计算,保持列“干净”,比如
FROM_unixtime(create_time) = ’2014-05-29’
就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。语句应该写成create_time = unix_timestamp(’2014-05-29’)。 - 尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
索引的使用场景
-
对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效。
-
对于中到大型的表,索引就非常有效。
-
但是对于特大型的表,建立和维护索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。
什么情况不要使用索引
-
经常增删改的列不要建立索引。
-
有大量重复的列不建立索引。
-
表记录太少不要建立索引。
回表和索引覆盖
在InnoDB的存储引擎中,使用辅助索引查询的时候,因为辅助索引叶子节点保存的数据不是当前记录的数据而是当前记录的主键索引,索引如果需要获取当前记录完整数据就必然需要根据主键值从主键索引继续查询。这个过程称为回表。回表必然是会消耗性能影响性能。那如何避免呢?
可以使用索引覆盖解决,举个例子:现有User表(id(PK),name(key),sex,address,hobby…)
如果在一个场景下,SELECT id,name,sex FROM user AND name ='zhangsan';
这个语句在业务上频繁使用到,而user表的其他字段使用频率远低于它,在这种情况下,如果我们在建立 name 字段的索引的时候,不是使用单一索引,而是使用联合索引(name,sex)这样的话再执行这个查询语句是不是根据辅助索引查询到的结果就可以获取当前语句的完整数据。这样就可以有效地避免了回表再获取sex的数据。
这就是一个典型的使用覆盖索引的优化策略减少回表的情况。
联合索引的使用
联合索引,在建立索引的时候,尽量在多个单列索引上判断下是否可以使用联合索引。联合索引的使用不仅可以节省空间,还可以更容易的使用到索引覆盖。试想一下,索引的字段越多,是不是更容易满足查询需要返回的数据呢。比如联合索引(a_b_c),是不是等于有了索引:a,a_b,a_b_c三个索引,这样是不是节省了空间,当然节省的空间并不是三倍于(a,a_b,a_b_c)三个索引,因为索引树的数据没变,但是索引data字段的数据确实真实的节省了。
联合索引的创建原则,在创建联合索引的时候因该把频繁使用的列、区分度高的列放在前面,频繁使用代表索引利用率高,区分度高代表筛选粒度大,这些都是在索引创建的需要考虑到的优化场景,也可以在常需要作为查询返回的字段上增加到联合索引中,如果在联合索引上增加一个字段而使用到了覆盖索引,那建议这种情况下使用联合索引。
联合索引的使用:
-
考虑当前是否已经存在多个可以合并的单列索引,如果有,那么将当前多个单列索引创建为一个联合索引。
-
当前索引存在频繁使用作为返回字段的列,这个时候就可以考虑当前列是否可以加入到当前已经存在索引上,使其查询语句可以使用到覆盖索引。
MySQL的约束
在MySQL中,约束实际上就是表中数据的限制条件,是指对表中数据的一种约束。能够帮助数据库管理员更好地管理数据库,并且能够确保数据库中数据的正确性和有效性。
例如,在数据表中存放年龄
的值时,如果的是存入 200、300
那这些相对于现实年龄来说其值就毫无意义。因此,使用约束来限定表中的数据范围是很有必要的。
表在设计的时候加入约束的目的就是为了保证表中的记录完整性和有效性。
比如用户表有些列的值(手机号)不能为空,有些列的值(身份证号)不能重复。如果插入的数据为空就会导致插不进去。
常见的约束分类
-
主键约束(primary key) PK
-
自增长约束(auto_increment)
-
非空约束(not null)
-
唯一性约束(unique)
-
默认约束(default)
-
零填充约束(zerofill)
-
外键约束(foreign key) FK
主键约束详解
主键约束概念
1.MySQL主键约束是一个列或者多个列的组合,其值能唯一的标识表中的每一行
,方便在关系型数据库(RDBMS)中尽快的找到某一行。
2.主键约束相当于 唯一约束 + 非空约束
的组合,主键约束列不允许重复,也不允许出现空值。
3.每个表最多只允许一个主键
,主键约束的关键字是:primary key
4.当创建主键的约束时,系统默认会在所在的列和列组合上建立对应的唯一索引
。
总结:主键的作用是在列上加上一个标记来标识这一行
,从而来提高查询速度
。其次标识的主键这一列或者多列,必须唯一而且都不能为空
。而且每个表最多只允许一个主键或者联合主键
,其主键约束的关键字是primary key
。
主键约束的相关操作
添加单列主键、添加多列联合主键、删除主键。
添加单列主键
创建单列主键有两种方式:
-
一种是在定义字段的同时指定主键。
-
一种是定义完字段之后指定主键。
方式一:
-- ----------语法1:定义字段的同时指定主键 ------------
-- 在 create table 语句中,通过 PRIMARY KEY 关键字来指定主键。
--在定义字段的同时指定主键,语法格式如下:
create table 表名(
...
<字段名> <数据类型> primary key
...
)
-- -----------语法1实现--------------
create table emp1(
eid int primay key,
name VARCHAR(20),
deptId int,
salary double
);
方式二:
-- ----------语法2:定义字段之后再指定主键 ------------
-- 在定义字段之后再指定主键,语法格式如下:
create table 表名(
...
[constraint <约束名>] primary key [字段名]
);
-- -----------语法2实现--------------
create table emp2(
eid INT,
name VARCHAR(20),
deptId INT,
salary double,
constraint pk1 primary key(id)
);
添加多列主键(联合主键)
所谓的联合主键,就是这个主键是由一张表中多个字段组成的。
注意:
-
当主键是由多个字段组成时,不能直接在字段名后面声明主键约束。
-
一张表只能有一个主键,联合主键也是一个主键。
语法与实现:
-- 语法
create table 表名(
...
primary key (字段1,字段2,…,字段n)
);
-- 实现:
create table emp3(
name varchar(20),
deptId int,
salary double,
primary key(name,deptId)
);
通过修改表结构添加主键
主键约束不仅仅可以在创建表的同时创建,也可以在修改表的时候添加主键。
-- 语法
create table 表名(
...
);
alter table <表名> add primary key(字段列表);
-- 实现
-- 添加单列主键
create table emp4(
eid int,
name varchar(20),
deptId int,
salary double,
);
alter table emp4 add primary key(eid);
删除主键
一个表中不需要主键约束时,就需要从表中将其删除。删除主键约束的方法要比创建主键约束容易的多。
-- 格式
alter table <数据表名> drop primary key;
-- 实现
-- 删除单列主键
alter table emp1 drop primary key;
-- 删除联合主键
alter table emp5 drop primary key;
只要drop删除 主键关键字primary key就可以了,不管是创建的是单列主键还是联合主键。
MySQL锁的类型
分两个维度:
-
共享锁(简称S锁)和排他锁(简称X锁)
- 读锁是共享的,可以通过lock in share mode实现,这时候只能读不能写。
- 写锁是排他的,它会阻塞其他的写锁和读锁。从颗粒度来区分,可以分为表锁和行锁两种。
-
表锁和行锁
-
表锁会锁定整张表并且阻塞其他用户对该表的所有读写操作,比如alter修改表结构的时候会锁表。
-
行锁
又可以分为乐观锁和悲观锁
- 悲观锁可以通过for update实现。
- 乐观锁则通过版本号实现。
-
两个维度结合来看:
- 共享锁(行锁):Shared Locks
- 读锁(s锁),多个事务对于同一数据可以共享访问,不能操作修改
- 使用方法:
- 加锁:SELECT * FROM table WHERE id=1 LOCK IN SHARE MODE
- 释锁:COMMIT/ROLLBACK
- 排他锁(行锁):Exclusive Locks
- 写锁(X锁),互斥锁/独占锁,事务获取了一个数据的X锁,其他事务就不能再获取该行的读锁和写锁(S锁、X锁),只有获取了该排他锁的事务是可以对数据行进行读取和修改
- 使用方法:
- DELETE/ UPDATE/ INSERT – 加锁
- SELECT * FROM table WHERE … FOR UPDATE – 加锁
- COMMIT/ROLLBACK – 释锁
- 意向共享锁(IS)
- 一个数据行加共享锁前必须先取得该表的IS锁,意向共享锁之间是可以相互兼容的 意向排它锁(IX) 一个数据行加排他锁前必须先取得该表的IX锁,意向排它锁之间是可以相互兼容的 意向锁(IS、IX)是InnoDB引擎操作数据之前自动加的,不需要用户干预; 意义: 当事务操作需要锁表时,只需判断意向锁是否存在,存在时则可快速返回该表不能启用表锁
- 意向共享锁(IS锁)(表锁):Intention Shared Locks
- 表示事务准备给数据行加入共享锁,也就是说一个数据行加共享锁 前必须先取得该表的IS锁。
- 意向排它锁(IX锁)(表锁):Intention Exclusive Locks
- 表示事务准备给数据行加入排他锁,说明事务在一个数据行加排他 锁前必须先取得该表的IX锁。
性能优化
索引的优化
使用索引的注意事项
1、应尽量避免在 WHERE 子句中用 != 或 < > 操作符,否则将导致引擎放弃使用索引而进行全表扫描。优化器将无法通过索引来确定将要命中的行数,因此需要搜索该表的所有行。
2、应尽量避免在 WHERE 子句中使用 OR 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如: SELECT id FROM t WHERE num = 10 OR num = 20。
3、应尽量避免在 WHERE 子句中对字段进行表达式操作,否则将导致引擎放弃使用索引而进行全表扫描。
4、应尽量避免在 WHERE 子句中对字段进行函数提作,否则将导致引擎放弃使用索引而进行全表扫描。
5、应尽量避免在 WHERE 子句中对字段进行NULL值判断,否则将导致引擎放弃使用索引而进行全表扫描。
6、应尽量避免在 WHERE 子句中使用OR来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描。
7、不要在 WHERE 子句中的 = 左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
8、复合索引遵循最左前缀原则。
9、如果 MySQL 评估使用素引比全表扫描更慢,会放弃使用索引。如果此时想要索引,可以在语句中添加强制索引。
10、列类型是字符串类型,查询时一定要给值加引号,否则索引失效。
11、LIKE 查询,% 不能在前,因为无法使用索引。如果需要模糊匹配,可以使用全文索引。
12、表字段为NULL 也是不可以使用索引的。
13、字段是字符串类型的使用的时候,必须加引号,否则索引失效。
SQL语句调优
1、对查询进行优化,应尽量避免全表扫描,首先考虑在where及order by上建立索引。
2、应尽量避免在where子句中进行以下操作:对字段进行null判断;使用!=或<>操作符;使用or连接条件;使用in或not in;使用like;等号左侧使用算术运算;对字段进行函数运算等。以上操作将导致引擎放弃索引而进行全表扫描。
3、不要写一些没有意义的查询,如生成一个空表。
4、使用exists替代in,用not exists替代not in。not in 是低效的,因为它对子查询中的表执行了一个全表遍历,它执行了一个内部的排序和合并。select num from a where exists(select 1 from b where num=a.num)
5、对只含数值信息的字段尽量使用数值型代替字符型,否则会降低查询和连接性能。
6、尽可能使用varchar代替char,节约存储空间,提高效率。
7、尽量用具体字段代替*进行查询。
8、在使用索引字段作为条件时,如果索引是复合索引,必须使用该索引的第一个字段作为条件才能保证系统使用该索引。
9、当索引中有大量重复数据时,索引是无效的。
10、当进行update或insert操作时,索引的存在会降低该操作的效率。
11、尽量避免频繁创建或删除临时表,减少系统资源消耗。
12、在新建临时表时,如果一次性插入数据量很大,那么可以使用select into代替create table,避免产生大量log,提高效率。
13、如果使用到了临时表,在存储过程的最后务必将所有的临时表显示的删除,先truncate table ,然后drop table,避免系统表长时间锁定。
14、尽量避免使用游标,因为游标效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
15、对于小型数据集使用fast_forward游标要优于其他逐行处理方法,尤其是在必须引用几个表才能获取所需要的数据时。
16、表名顺序。选择最有效率的表名顺序,from后面先跟大表,再跟小表,因为from子句中写在最后的表被优先处理,from后跟多个表的情况下,应该选择记录条数最少的表作为优先处理的表。
17、where子句连接顺序。那些可以过滤最大数量记录的条件必须写在where子句的末尾。
18、高效删除重复记录。最高效的删除重复记录方法 ( 因为使用了ROWID)例子:delete from emp a where a.rowid>(select min(b.rowid) from emp b where a.emp_no=b.emp_no)
19、使用truncate替代delete。当用delete删除表中记录时,回滚段rollback用来被存放可以被恢复的信息,如果你不执行commit,oracle会将数据恢复到删除之前的状态;当运行truncate时,回滚段不再存放任何可被恢复的信息,当运行truncate时,数据不再被恢复,此时很少的资源被调用,执行时间也会很短。
20、尽量多使用commit。随着commit的多次使用,系统资源被释放,性能会提高。
21、用where子句替换having子句。having只会在检索出所有记录之后才对结果集进行过滤。on、where、having这三个都是删选条件的子句,on最先执行,where次之,hiving最后;on先把不符合条件的记录过滤才进行统计,它可以减少中间运算要处理的数据,on的使用仅限于多表连接;where也是过滤数据后才进行sum;hiving是在计算之后才启作用。
22、使用表的别名。当sql语句中连接多个表时,请使用表的别名并用别名前缀识别每个column,这样可以减少sql解析时间,避免歧义。
23、用 >= 替代 >
或 <= 代替 <
高效:SELECT * FROM EMP WHERE DEPTNO >=4 低效: SELECT * FROM EMP WHERE DEPTNO >3
前者DBMS将直接跳到第一个DEPT等于4的记录而后者将首先定位到DEPTNO=3的记录并且向前扫描到第一个DEPT大于3的记录。
24、用union all替换union。当SQL语句需要UNION两个查询结果集合时,这两个结果集合会以UNION-ALL的方式被合并,然后在输出最终结果前进行排序。如果用UNION ALL替代UNION,这样排序就不是必要了。效率就会因此得到提高。
25、当使用join(inner join或left join)操作时,就应该是小表在前,大表在后。把重复关联键少的表放在join前面做关联可以提高join的效率。
26、优化insert语句,批量列插入数据要比单个列插入数据效率高。
27、优化order by语句,在使用order by语句时,不要使用select *
,select后面要查有索引的列,如果一条sql语句中对多个列进行排序,在业务允许情况下,尽量同时用升序或同时用降序。
28、优化group by语句,在对某一个字段进行分组的时候,MySQL默认就进行了排序,但是排序可以并不是我们业务所需的,额外的排序会降低效率,所以在用的时候可以禁止排序,使用order by null禁用。
29、根据业务场景建立复合索引只查询业务需要的字段,如果这些字段被索引覆盖,将极大的提高查询效率。
30、多表连接的字段上需要建立索引,这样可以极大提高表连接的效率。
优化数据访问
减少请求的数据量
- 只返回必要的列: 最好不要使用 SELECT * 语句。
- 只返回必要的行: 使用 LIMIT 语句来限制返回的数据。
- 缓存重复查询的数据: 使用缓存可以避免在数据库中进行查询,特别在要查询的数据经常被重复查询时,缓存带来的查询性能提升将会是非常明显的。
减少服务器端扫描的行数
最有效的方式是使用索引来覆盖查询。
重构查询方式
切分大查询
一个大查询如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。
DELEFT FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH);
rows_affected = 0
do {
rows_affected = do_query(
"DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH) LIMIT 10000")
} while rows_affected > 0
分解大连接查询
将一个大连接查询分解成对每一个表进行一次单表查询,然后将结果在应用程序中进行关联,这样做的好处有:
- 让缓存更高效。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用。
- 分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余记录的查询。
- 减少锁竞争;
- 在应用层进行连接,可以更容易对数据库进行拆分,从而更容易做到高性能和可伸缩。
- 查询本身效率也可能会有所提升。例如下面的例子中,使用 IN() 代替连接查询,可以让 MySQL 按照 ID 顺序进行查询,这可能比随机的连接要更高效。
SELECT * FROM tab
JOIN tag_post ON tag_post.tag_id=tag.id
JOIN post ON tag_post.post_id=post.id
WHERE tag.tag='mysql';
SELECT * FROM tag WHERE tag='mysql';
SELECT * FROM tag_post WHERE tag_id=1234;
SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904);
慢查询优化
定位慢SQL语句
-
show status like ‘Com’;
通过以上命令, 可以知道当前数据库是以查询为主还是更新为主,如果是查询为主,就重点查询;如果增删改多就重点优化写入操作。
-
show profile;
分析 SQL,可以查看所有 sql 语句的执行效率(所用时间)。前提是这个命令需要被打开,严格的说也就是打开这个命令后执行的所有 sql 语句,它都能记录下执行时间,并展示出来。可以通过这个命令分析哪些 sql 语句执行效率低,耗时长,就更有针对性的优化这条 sql。
-
explain + sql语句;
查询sql执行过程,通过执行计划,可以得到这些信息:
-
哪些步骤花费的成本比较高
-
哪些步骤产生的数据量多,数据量的多少用线条的粗细表示,很直观
-
这条sql语句是否走索引
-
-
慢查询日志(常用的工具)
慢查询日志记录了有执行时间超过参数 long_query_time 的sql语句的日志,long_query_time 默认为 10 秒(可以通过配置文件设置),日志保存在 /var/lib/mysql/目录下,有个 slow_query.log 文件。
查看慢查询日志是否开启
show variables like ‘slow%’;
临时开启慢查询日志
set global slow_query_log = ON;
慢查询日志需要了解的参数:
slow_query_log # 是否开启慢查询日志,默认OFF,开启则设置为 ON。
slow_query_log_file # 慢查询日志文件存储位置。
log_queries_not_using_indexes # 是否把没有使用到索引的SQL记录到日志中,默认OFF,开启则设置为 ON。
long_query_time # 超过多少秒的查询才会记录到日志中,注意单位是秒。
方式一:命令全局设置,Mysql重启失效
知道参数含义后,我们就可以直接设置我们需要的参数了,我这里使用的是全局设置。打开查询界面,执行下面语句
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL slow_query_log_file = '文件路径(绝对路径)';
SET GLOBAL log_queries_not_using_indexes = 'ON';
SET GLOBAL long_query_time = 1; # 这里需要注意下,long_query_time参数设置后需要下次会话后才生效,当前会话查询还是原来的数值
方式二:到mysql的安装配置文件my.cnf中【mysqlid】位置处,设置参数,然后重启【永久有效】,my.cnf
的位置一般再/etc/my.cnf
配置文件中添加参数,重启mysql
slow_query_log="ON"
slow_query_log_file="文件路径(绝对路径)"
log_queries_not_using_indexes="ON"
long_query_time=1
慢查询优化神器 - explain 命令
关于explain命令的具体用法和字段含义可以参考官网explain-output,这里需要强调rows是核心指标,绝大部分rows小的语句执行一定很快。所以优化语句基本上都是在优化rows。
Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explain 结果来优化查询语句。
比较重要的字段有:
- select_type : 查询类型,有简单查询、联合查询、子查询等
- key : 使用的索引
- rows : 扫描的行数
慢查询优化基本步骤
0.先运行看看是否真的很慢,注意设置SQL_NO_CACHE
1.AND条件单表查,锁定最小返回记录表。这句话的意思是把查询语句的AND都应用到表中返回的记录数最小的表开始查起,单表每个字段分别查询,看哪个字段的区分度最高
2.explain查看执行计划,是否与1预期一致(从锁定记录较少的表开始查询)
3.order by limit 形式的sql语句让排序的表优先查
4.了解业务方使用场景
5.加索引时参照建索引的几大原则
6.观察结果,不符合预期继续从0分析
分库分表
首先分库分表分为垂直和水平两个方式,一般来说拆分的顺序是先垂直后水平。
- 垂直分库
基于现在微服务拆分来说,都是已经做到了垂直分库了。
- 垂直分表
垂直切分是将一张表按列切分成多个表,通常是按照列的关系密集程度进行切分,也可以利用垂直切分将经常被使用的列和不经常被使用的列切分到不同的表中。
在数据库的层面使用垂直切分将按数据库中表的密集程度部署到不同的库中,例如将原来的电商数据库垂直切分成商品数据库、用户数据库等。
- 水平分表
首先根据业务场景来决定使用什么字段作为分表字段(sharding_key),比如我们现在日订单1000万,我们大部分的场景来源于C端,我们可以用user_id作为sharding_key,数据查询支持到最近3个月的订单,超过3个月的做归档处理,那么3个月的数据量就是9亿,可以分1024张表,那么每张表的数据大概就在100万左右。
比如用户id为100,那我们都经过hash(100),然后对1024取模,就可以落到对应的表上了。
分表后的ID如何保证唯一性
因为主键默认都是自增的,那么分表之后的主键在不同表就肯定会有冲突了。有几个办法考虑:
- 设定步长,比如1-1024张表我们分别设定1-1024的基础步长,这样主键落到不同的表就不会冲突了。
- 分布式ID,自己实现一套分布式ID生成算法或者使用开源的比如雪花算法这种。
- 分表后不使用主键作为查询依据,而是每张表单独新增一个字段作为唯一主键使用,比如订单表订单号是唯一的,不管最终落在哪张表都基于订单号作为查询依据,更新也一样。
分表后非sharding_key的查询如何处理
- 可以做一个mapping表,比如这时候商家要查询订单列表怎么办呢?不带user_id查询的话总不能扫全表吧?所以我们可以做一个映射关系表,保存商家和用户的关系,查询的时候先通过商家查询到用户列表,再通过user_id去查询。
- 大宽表,一般而言,商户端对数据实时性要求并不是很高,比如查询订单列表,可以把订单表同步到离线(实时)数仓,再基于数仓去做成一张宽表,再基于其他如es提供查询服务。
- 数据量不是很大的话,比如后台的一些查询之类的,也可以通过多线程扫表,然后再聚合结果的方式来做。或者异步的形式也是可以的。
List<Callable<List<User>>> taskList = Lists.newArrayList();
for (int shardingIndex = 0; shardingIndex < 1024; shardingIndex++) {
taskList.add(() -> (userMapper.getProcessingAccountList(shardingIndex)));
}
List<ThirdAccountInfo> list = null;
try {
list = taskExecutor.executeTask(taskList);
} catch (Exception e) {
//do something
}
public class TaskExecutor {
public <T> List<T> executeTask(Collection<? extends Callable<T>> tasks) throws Exception {
List<T> result = Lists.newArrayList();
List<Future<T>> futures = ExecutorUtil.invokeAll(tasks);
for (Future<T> future : futures) {
result.add(future.get());
}
return result;
}
}
主从复制
主要涉及三个线程: binlog 线程、I/O 线程和 SQL 线程。
- binlog 线程 : 负责将主服务器上的数据更改写入二进制日志中。
- I/O 线程 : 负责从主服务器上读取二进制日志,并写入从服务器的中继日志中。
- SQL 线程 : 负责读取中继日志并重放其中的 SQL 语句。
全同步复制
主库写入binlog后强制同步日志到从库,所有的从库都执行完成后才返回给客户端,但是很显然这个方式的话性能会受到严重影响。
半同步复制
和全同步不同的是,半同步复制的逻辑是这样,从库写入日志成功后返回ACK确认给主库,主库收到至少一个从库的确认就认为写操作完成。
读写分离
主服务器处理写操作以及实时性要求比较高的读操作,而从服务器处理读操作。
读写分离能提高性能的原因在于:
- 主从服务器负责各自的读和写,极大程度缓解了锁的争用;
- 从服务器可以使用 MyISAM,提升查询性能以及节约系统开销;
- 增加冗余,提高可用性。
读写分离常用代理方式来实现,代理服务器接收应用层传来的读写请求,然后决定转发到哪个服务器。
零散知识点
1, 在MySQL中一条查询SQL语句的执行流程
select name from t_user where id=1
-
建立连接,连接器会校验你输入的用户名和密码是否正确,如果错误会返回提示,如果正确,连接器会查询当前用户对于数据库的权限。连接器的作用就是校验用户权限。
-
查询缓存,key 为 SQL 语句,value 为查询结果,如果查到就直接返回。不建议使用次缓存,在 MySQL 8.0 版本已经将查询缓存删除,也就是说
MySQL 8.0 版本后不存在此功能
。 -
分析器,分为词法分析和语法分析。此阶段只是做一些 SQL 解析,语法校验。所以一般语法错误在此阶段。
-
优化器,是在表里有多个索引的时候,决定使用哪个索引;或者一个语句中存在多表关联的时候(join),决定各个表的连接顺序。
-
执行器,通过分析器让 SQL 知道你要干啥,通过优化器知道该怎么做,于是开始执行语句。执行语句的时候还要判断是否具备此权限,没有权限就直接返回提示没有权限的错误;有权限则打开表,根据表的引擎定义,去使用这个引擎提供的接口,获取这个表的第一行,判断 id 是都等于 1。如果是,直接返回;如果不是继续调用引擎接口去下一行,重复相同的判断,直到取到这个表的最后一行,最后返回。
2, 查询语句的语法顺序和执行顺序
语法顺序
select语句的语法格式如下:
- select 字段列表
- from 数据源
- [ where条件表达式 ]
- [ group by 分组字段 ]
- [ having条件表达式 ]
- [ order by 排序字段 [ asc | desc ] ]
执行顺序
-
FROM 对FROM的左边的表和右边的表计算笛卡尔积。产生虚表VT1。
-
ON 对虚表VT1进行ON筛选,只有那些符合的行才会被记录在虚表VT2中。
-
JOIN 如果指定了OUTER JOIN(比如left join、 right join),那么保留表中未匹配的行就会作为外部行添加到虚拟表VT2中,产生虚拟表VT3, rug from子句中包含两个以上的表的话,那么就会对上一个join连接产生的结果VT3和下一个表重复执行步骤1~3这三个步骤,一直到处理完所有的表为止。
-
WHERE 对虚拟表VT3进行WHERE条件过滤。只有符合的记录才会被插入到虚拟表VT4中。
-
GROUP BY 根据group by子句中的列,对VT4中的记录进行分组操作,产生VT5。
-
WITH CUBE or WITH ROLLUP 对表VT5进行cube或者rollup操作,产生表VT6。
-
HAVING 对虚拟表VT6应用having过滤,只有符合的记录才会被 插入到虚拟表VT7中。
-
SELECT 执行select操作,选择指定的列,插入到虚拟表VT8中。
-
DISTINCT 对VT8中的记录进行去重。产生虚拟表VT9。
-
ORDER BY 将虚拟表VT9中的记录按照<order_by_list>进行排序操作,产生虚拟表VT10。
-
TOP 取出指定行的记录,产生虚拟表VT11, 并将结果返回。
这些步骤执行时,每个步骤都会产生一个虚拟表
,该虚拟表被用作下一个步骤的输入.这些虚拟表对调用者(客户端应用程序或者外部查询)不可用。只是最后一步生成的表才会返回给调用者
。
3, delete、truncate和drop的区别
drop:删除整张表。
delete:删除表中的行,只要事务没提交就可以回滚,记录undolog,性能相对于truncate要差一些。
truncate:删除表中的行,把表中的数据全部清空,并且无法回滚,不会记录undolog,性能是非常高的。
4, MySQL大分页优化
-
使用覆盖索引
SELECT * FROM alarm_base WHERE id >=(SELECT id FROM alarm_base limit 100000,1 ORDER BY id) ORDER BY id limit 10
-
使用子查询
SELECT t1.* FROM 表 as t1, (select id from 表 where 条件 LIMIT 100000,10 ) as t2 where t1.id=t2.id
5, 怎样尽量避免死锁的出现
-
设置获取锁的超时时间,至少能保证最差情况下,可以退出程序,不至于一直等待导致死锁;
-
设置按照同一顺序访问资源,类似于串行执行;
-
避免事务中的用户交叉;
-
保持事务简短并在一个批处理中;
-
使用低隔离级别;
-
使用绑定链接。
6, 主键与索引有什么区别
-
主键一定会创建一个唯一索引,但是有唯一索引的列不一定是主键;
-
主键不允许为空值,唯一索引列允许空值;
-
一个表只能有一个主键,但是可以有多个唯一索引;
-
主键可以被其他表引用为外键,唯一索引列不可以;
-
主键是一种约束,而唯一索引是一种索引,是表的冗余数据结构,两者有本质的区别。
7, 什么是MVCC
MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
在Mysql的InnoDB引擎中就是指在已提交读(READ COMMITTD)和可重复读(REPEATABLE READ)这两种隔离级别下的事务对于SELECT操作会访问版本链中的记录的过程。
这就使得别的事务可以修改这条记录,反正每次修改都会在版本链中记录。SELECT可以去版本链中拿记录,这就实现了读-写,写-读的并发执行,提升了系统的性能。
MySQL的InnoDB引擎实现MVCC的3个基础点:隐式字段、undo log、ReadView
已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在第一次读的时候生成一个ReadView,之后的读都复用之前的ReadView。
8, MySQL如何做到高可用方案
MySQL 高可用,意味着不能一台 MySQL 出了问题,就不能访问了。
-
MySQL 高可用:分库分表,通过 MyCat 连接多个 MySQL
-
MyCat 也可以实现高可用:Haproxy,连接多个 MyCat
-
Haproxy 也可以实现高可用:通过 keepalived 辅助 Haproxy
先写到这里,后续再补充更新。