来源在这里
来源在这里
1.Redis的优势
(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都很低
(2)支持丰富数据类型,支持string,list,set,sorted set,hash
(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
1.1 Redis有哪些优缺点?
优点
- 读写性能优异, Redis能读的速度是110000次/s,写的速度是81000次/s。
- 支持数据持久化,支持AOF和RDB两种持久化方式。
- 支持事务,Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性 执行。
- 数据结构丰富,除了支持string类型的value外还支持hash、set、zset、list等数据结构。
- 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
缺点
- 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
- Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
- 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
- Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。
1.2 Redis 为何这么快?
1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1);
2、数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路 I/O 复用模型,非阻塞 IO;
5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
1.3、为何使用单线程?
官方答案
因为 Redis 是基于内存的操作,CPU 不会成为 Redis 的瓶颈,而最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。
详细原因
- 绝大部分请求是纯粹的内存操作(非常快速)
2)采用单线程,避免了不必要的上下文切换和竞争条件
2.Redis里面的数据结构?
String(字符串)
Hash(哈希)
List(列表)
Set(集合)
zset(有序集合)
3.什么是缓存击穿、缓存穿透、缓存雪崩?
3.1 缓存穿透问题
先来看一个常见的缓存使用方式:读请求来了,先查下缓存,缓存有值命中,就直接返回;缓存没命中,就去查数据库,然后把数据库的值更新到缓存,再返回。
读取缓存
缓存穿透:指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。
通俗点说,读请求访问时,缓存和数据库都没有某个值,这样就会导致每次对这个值的查询请求都会穿透到数据库,这就是缓存穿透。
缓存穿透一般都是这几种情况产生的:
业务不合理的设计,比如大多数用户都没开守护,但是你的每个请求都去缓存,查询某个userid查询有没有守护。
业务/运维/开发失误的操作,比如缓存和数据库的数据都被误删除了。
黑客非法请求攻击,比如黑客故意捏造大量非法请求,以读取不存在的业务数据。
如何避免缓存穿透呢? 一般有三种方法。
1.如果是非法请求,我们在API入口,对参数进行校验,过滤非法值。2.如果查询数据库为空,我们可以给缓存设置个空值,或者默认值。但是如有有写请求进来的话,需要更新缓存哈,以保证缓存一致性,同时,最后给缓存设置适当的过期时间。(业务上比较常用,简单有效)3.使用布隆过滤器快速判断数据是否存在。即一个查询请求过来时,先通过布隆过滤器判断值是否存在,存在才继续往下查。
布隆过滤器原理:它由初始值为0的位图数组和N个哈希函数组成。一个对一个key进行N个hash算法获取N个值,在比特数组中将这N个值散列后设定为1,然后查的时候如果特定的这几个位置都为1,那么布隆过滤器判断该key存在。
3.2缓存雪奔问题
缓存雪奔: 指缓存中数据大批量到过期时间,而查询数据量巨大,请求都直接访问数据库,引起数据库压力过大甚至down机。
缓存雪奔一般是由于大量数据同时过期造成的,
对于这个原因,可通过均匀设置过期时间解决,即让过期时间相对离散一点。如采用一个较大固定值+一个较小的随机值,5小时+0到1800秒酱紫。
Redis 故障宕机也可能引起缓存雪奔。这就需要构造Redis高可用集群啦。
3.3 缓存击穿问题
缓存击穿: 指热点key在某个时间点过期的时候,而恰好在这个时间点对这个Key有大量的并发请求过来,从而大量的请求打到db。
缓存击穿看着和缓存雪崩有点像,其实它两区别是,
缓存雪奔是指数据库压力过大甚至down机,
缓存击穿只是大量并发请求到了DB数据库层面。
可以认为击穿是缓存雪奔的一个子集吧。
有些文章认为它俩区别,是区别在于击穿针对某一热点key缓存,雪奔则是很多key。
解决方案就有两种:
1.使用互斥锁方案。缓存失效时,不是立即去加载db数据,而是先使用某些带成功返回的原子操作命令,如(Redis的setnx)去操作,成功的时候,再去加载db数据库数据和设置缓存。否则就去重试获取缓存。
2. “永不过期”,是指没有设置过期时间,但是热点数据快要过期时,异步线程去更新和设置过期时间。
4.HashTable访问的时间复杂度?
如果对Hashtable的构造有很深的理解的话,就知道了,
Hashtable 其实是综合了数组和链表的优点,
当Hashtable对数值进行搜索的时候,首先用该数值与Hashtable的长度做了取模的操作,得到的数字直接作为hashtable中entry数组的index,因为hashtable是由entry数组组成的,因此,可以直接定位到指定的位置,不需要搜索,当然,这里还有个问题,每个entry其实是链表,如果entry有很多值的话,还是需要挨个遍历的,
因此可以这样讲Hashtable的时间复杂度最好是O(1)但是最差是 O(n) 最差的时候也就是hashtable中所有的值的hash值都一样,都分配在一个entry里面,当然这个概率跟中1亿彩票的概率相差不大。
来自原答案
答错成O(n),在插入、查找或删除操作时是O(1),除非存在哈希冲突的情况下,哈希表的访问时间复杂度可能会略微增加,最坏情况下可能达到O(n)
5.数组和链表的区别?
(1)元素个数上来看,
数组的元素个数是固定的,而组成链表的结点个数可按需要增减;
(2)存储空间的分配来看,
数组元素的存诸单元在数组定义时分配,在使用前要先申请占内存的大小,可能会浪费内存空间。
链表结点的存储单元空间不需要提前指定大小,是动态申请的,根据需求动态的申请和删除内存空间,扩展方便,故空间的利用率较高
(3)在内存中,
数组是一块连续的区域。
链表在内存中可以存在任何地方,不要求连续。
(4)元素顺序
数组中的元素顺序关系由元素在数组中的位置(即下标)确定,
链表中的结点顺序关系由结点所包含的指针来体现。
(5)查询效率
链表的查找数据时效率低,时间复杂度为O(N)
因为链表的空间是分散的,所以不具有随机访问性,如要需要访问某个位置的数据,需要从第一个数据开始找起,依次往后遍历,直到找到待查询的位置,故可能在查找某个元素时,时间复杂度达到O(N)
数组的随机访问效率很高,时间复杂度可以达到O(1)
因为数组的内存是连续的,想要访问那个元素,直接从数组的首地址处向后偏移就可以访问到了
(6)插入删除的效率
在数组起始位置处,插入数据和删除数据效率低。
特别是头插和头删的效率低,时间复杂度为O(N)。
插入数据时,待插入位置的的元素和它后面的所有元素都需要向后搬移
删除数据时,待删除位置后面的所有元素都需要向前搬移。
链表在任意位置插入元素和删除元素效率较高,时间复杂度为O(1)
对于元素的插插入、删除操作非常频繁的列表处理场合,用数组表示列表也是不适宜的。若用链表实现,会使程序结构清晰,处理的方法也较为简便。
例如在一个列表中间要插人一个新元素,如用数组表示列表,为完成插人工作,插人处之后的全部元素必须向后移动一个位置空出的位置用于存储新元素。
对于在一个列表中删除一个元素情况,为保持数组中元素相对位置连续递增,删除处之后的元素都得向前移一个位置。如用链表实现列表.链表结点的插人或删除操作不再需要移动结点,只需改变相关的结点中的后继结点指针的值即可,与结点的实际存储位置无关。
(7)空间分配
数组的空间是从栈分配的,链表的空间是从堆中分配的。
6.说一下MySQL增删改查的语句?
6.1添加数据
格式:insert into 表名[(字段列表)] values(值列表...);
注:[]中为可省略的数据
– 标准添加(指定所有字段,给定所有的值)
insert into stu(id,name,age,sex,classid) values(1,'zhangsan',20,'男','1');
6.2修改数据
格式:update 表名 set 字段1=值1,字段值2=值2,字段n=值n ...where 条件
– 将id为2的age改为22,sex改为男
update stu set age=22,sex='男' where id=2;
6.3删除数据
格式:delete from 表名 [where 条件]
– 删除stu表中id值为11的数据
delete from stu where id=11;
– 删除stu表中id值为12到17的数据
delete from stu where id between 12 and 17;
6.4查询语句
语法格式:
select 字段列表|* from 表名
[where 搜索条件]
[group by 分组字段 [having 分组条件]]
[order by 排序字段 排序规则]
[limit 分页参数]
6.4.1基础查询
查询表中所有列 所有数据
select * from stu;
指定字段列表进行查询
select id,name,age,sex from stu;
6.4.2Where 条件查询
可以在where子句中指定任何条件
可以使用 and 或者 or 指定一个或多个条件
where条件也可以运用在update和delete语句的后面
where子句类似程序语言中if条件,根据mysql表中的字段值来进行数据的过滤
示例:
-- 查询stu表中 age > 22的数据
select * from stu where age>22;-- 查询 stu 表中 name=某个条件值 的数据
select * from stu where name='yyx';-- 查询 stu 表中 年龄在22到25之间的数据
select * from stu where age between 22 and 25;
6.4.3 and和or 使用时注意 (and 优先级大于 or)
假设要求 查询 users 表中 年龄为22或者25 的女生信息
– 如何改造sql符合我们的查询条件呢?
– 使用小括号来关联相同的条件
select * from users where (age=22 or age = 25) and sex = '女';
6.4.3Like 子句
我们可以在where条件中使用=,<,> 等符合进行条件的过滤,但是当想查询某个字段是否包含时如何过滤?
可以使用like语句进行某个字段的模糊搜索,
-- 例如: 查询 name字段中包含五的数据-- like 语句 like某个确定的值 和。where name = '王五' 是一样select * from stu where name like 'yyx';-- 使用 % 模糊搜索。%代表任意个任意字符-- 查询name字段中包含x的select * from stu where name like '%x%';-- 查询name字段中最后一个字符 为 u的select * from stu where name like '%u';-- 查询name字段中第一个字符 为 q 的select * from stu where name like 'q%';-- 使用 _ 单个的下划线。表示一个任意字符,使用和%类似-- 查询表中 name 字段为两个字符的数据select * from users where name like '__';-- 查询 name 字段最后为j,的两个字符的数据select * from users where name like '_j';
注意:where子句中的like在使用%或者_进行模糊搜索时,效率不高,使用时注意:
尽可能的不去使用%或者_
如果需要使用,也尽可能不要把通配符放在开头处
7.数据库的聚合函数是什么?配合什么关键字一起使用?
-
什么是聚合函数
聚合函数作用于一组数据,并对一组数据返回一个值。
1.每个组函数接收一个参数(字段名或者表达式)统计结果中默认忽略字段为NULL的记录
2.要想列值为NULL的行也参与组函数的计算,必须使用IFNULL函数对NULL值做转换。
3.不允许出现嵌套 比如sum(max(xx))
- AVG() :只适用于数值类型的字段或变量。不包含NULL值
- SUM() :只适用于数值类型的字段或变量。不包含NULL值
- MAX() :适用于数值类型、字符串类型、日期时间类型的字段(或变量)不包含NULL值
- MIN() :适用于数值类型、字符串类型、日期时间类型的字段(或变量)不包含NULL值
- COUNT() :计算指定字段在查询结构中出现的个数(不包含NULL值)
聚合函数除了简单的使用以外,通常情况下都是配合着分组进行数据的统计和计算
聚合函数和分组一起使用
非法使用聚合函数 : 不能在 WHERE 子句中使用聚合函数
select count(*),group_concat(age) from students group by age;
7.1 having子句
having时在分组聚合计算后,对结果再一次进行过滤,类似于where,
where过滤的是行数据,having过滤的是分组数据
-- 要统计班级人数
select classid,count(*) from stu group by classid;-- 统计班级人数,并且要人数达到5人及以上
select classid,count(*) as num from stu group by classid having num >=5
关键字 | 优点 | 缺点 |
---|---|---|
WHERE | 先筛选数据再关联,执行效率高 | 不能使用分组中的计算函数进行筛选 |
Having | 可以使用分组中的计算函数 | 在最后的结果集中进行筛选,执行效率较低 |
8.mysql排序的关键字?
orded by
ORDER BY 语句用于根据指定的列对结果集进行排序。
ORDER BY 语句默认按照升序对记录进行排序。如果您希望按照降序对记录进行排序,可以使用 ASC(升序)、DESC(降序) 关键字。
9.什么是慢查询
慢查询,顾名思义,执行很慢的查询。有多慢?超过 long_query_time 参数设定的时间阈值(默认10s),就被认为是慢的,是需要优化的。慢查询被记录在慢查询日志里。
然而,慢查询日志默认是不开启的,也就是说一般人没玩过这功能。如果你需要优化SQL语句,就可以开启这个功能,它可以让你很容易地知道哪些语句是需要优化的(想想一个SQL要10s就可怕)。
9.1慢查询的解决方案
作者:千锋教育
链接:https://www.zhihu.com/question/19897912/answer/3156086987
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- SQL没加索引很多时候,我们的慢查询,都是因为没有加索引。如果没有加索引的话,会导致全表扫描的。因此,应考虑在where的条件列,建立索引,尽量避免全表扫描。
- 反例:
select * from user_info where name ='捡田螺的小男孩公众号' ;
正例://添加索引
alter table user_info add index idx_name (name);
2. SQL 索引不生效有时候我们明明加了索引了,但是索引却不生效。在哪些场景,索引会不生效呢?主要有以下十大经典场景:
2.1 隐式的类型转换,索引失效我们创建一个用户
user表CREATE TABLE user (id int(11) NOT NULL AUTO_INCREMENT,userId varchar(32) NOT NULL,age varchar(16) NOT NULL,name varchar(255) NOT NULL,PRIMARY KEY (id),KEY idx_userid (userId) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
userId字段为字串类型,是B+树的普通索引,如果查询条件传了一个数字过去,会导致索引失效。
如下:
如果给数字加上’',也就是说,传的是一个字符串呢,当然是走索引,如下图:
为什么第一条语句未加单引号就不走索引了呢?这是因为不加单引号时,是字符串跟数字的比较,它们类型不匹配,MySQL会做隐式的类型转换,把它们转换为浮点数再做比较。隐式的类型转换,索引会失效。
2.2 查询条件包含or,可能导致索引失效我们还是用这个表结构:
CREATE TABLE user (id int(11) NOT NULL AUTO_INCREMENT,userId varchar(32) NOT NULL,age varchar(16) NOT NULL,name varchar(255) NOT NULL,PRIMARY KEY (id),KEY idx_userid (userId) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
其中userId加了索引,但是age没有加索引的。我们使用了or,以下SQL是不走索引的,如下:
对于or+没有索引的age这种情况,假设它走了userId的索引,但是走到age查询条件时,它还得全表扫描,也就是需要三步过程:全表扫描+索引扫描+合并。如果它一开始就走全表扫描,直接一遍扫描就完事。Mysql优化器出于效率与成本考虑,遇到or条件,让索引失效,看起来也合情合理嘛。注意:如果or条件的列都加了索引,索引可能会走也可能不走,大家可以自己试一试哈。但是平时大家使用的时候,还是要注意一下这个or,学会用explain分析。遇到不走索引的时候,考虑拆开两条SQL。
2.3. like通配符可能导致索引失效。并不是用了like通配符,索引一定会失效,而是like查询是以%开头,才会导致索引失效。like查询以%开头,索引失效explain select * from user where userId like ‘%123’;
把%放后面,发现索引还是正常走的,如下:explain select * from user where userId like ‘123%’;
既然like查询以%开头,会导致索引失效。我们如何优化呢?使用覆盖索引把%放后面
2.4 查询条件不满足联合索引的最左匹配原则MySQl建立联合索引时,
会遵循最左前缀匹配的原则,即最左优先。
如果你建立一个(a,b,c)的联合索引,相当于建立了(a)、(a,b)、(a,b,c)三个索引。
假设有以下表结构:
CREATE TABLE user (id int(11) NOT NULL AUTO_INCREMENT,user_id varchar(32) NOT NULL,age varchar(16) NOT NULL,name varchar(255) NOT NULL,PRIMARY KEY (id),KEY idx_userid_name (user_id,name) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
有一个联合索引idx_userid_name,我们执行这个SQL,查询条件是name,索引是无效:explain select * from user where name ='捡田螺的小男孩';
因为查询条件列name不是联合索引idx_userid_name中的第一个列,索引不生效
在联合索引中,查询条件满足最左匹配原则时,索引才正常生效。
2.5 在索引列上使用mysql的内置函数表结构:
CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`userId` varchar(32) NOT NULL,`login_time` datetime NOT NULL,PRIMARY KEY (`id`),KEY `idx_userId` (`userId`) USING BTREE,KEY `idx_login_time` (`login_Time`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
虽然login_time加了索引,但是因为使用了mysql的内置函数Date_ADD(),索引直接GG,如图:
一般这种情况怎么优化呢?可以把内置函数的逻辑转移到右边,如下:
explain select * from user where login_time = DATE_ADD('2022-05-22 00:00:00',INTERVAL -1 DAY);
2.6 对索引进行列运算(如,+、-、*、/),索引不生效表结构:
CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`userId` varchar(32) NOT NULL,`age` int(11) DEFAULT NULL,PRIMARY KEY (`id`),KEY `idx_age` (`age`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
虽然age加了索引,但是因为它进行运算,索引直接迷路了。。。
如图:
所以不可以对索引列进行运算,可以在代码处理好,再传参进去。
2.7 索引字段上使用(!= 或者 < >),索引可能失效表结构
CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`userId` int(11) NOT NULL,`age` int(11) DEFAULT NULL,`name` varchar(255) NOT NULL,PRIMARY KEY (`id`),KEY `idx_age` (`age`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
;
虽然age加了索引,但是使用了!= 或者< >,not in这些时,索引如同虚设。
如下:
其实这个也是跟mySQL优化器有关,如果优化器觉得即使走了索引,还是需要扫描很多很多行的哈,它觉得不划算,不如直接不走索引。平时我们用!= 或者< >,not in的时候,留点心眼哈。
2.8 索引字段上使用is null, is not null,索引可能失效表结构:
CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`card` varchar(255) DEFAULT NULL,`name` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`),KEY `idx_name` (`name`) USING BTREE,KEY `idx_card` (`card`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
单个name字段加上索引,并查询name为非空的语句,其实会走索引的,
如下:
单个card字段加上索引,并查询name为非空的语句,其实会走索引的,
如下:
但是它两用or连接起来,索引就失效了,
如下:
很多时候,也是因为数据量问题,导致了MySQL优化器放弃走索引。同时,平时我们用explain分析SQL的时候,如果type=range,要注意一下哈,因为这个可能因为数据量问题,导致索引无效。
10.添加索引
创建索引使用: alter table 表名 add index 索引名[可选] (字段名, xxx);
删除索引使用: alter table 表名 drop index 索引名;
索引的分类:普通索引:最基本的索引,它没有任何限制唯一索引:索引列的值必须唯一,且不能为空,
如果是组合索引,则列值的组合必须唯一。主键索引:特殊的索引,唯一的标识一条记录,
不能为空,一般用primary key来约束。联合索引:在多个字段上建立索引,能够加速查询到速度。
普通索引
普通索引就是最基本的索引,没有任何限制。
可以使用命令创建普通索引:
ALTER TABLE `table_name` ADD INDEX index_name (`column`);
唯一索引
与普通索引不同,唯一索引的列值必须唯一,允许为null。
创建方式是这样的:
ALTER TABLE `table_name` ADD UNIQUE index_name (`column`);
主键索引
主键索引是一种特殊的唯一索引,并且一张表只有一个主键,不允许为null。
创建方式是这样的:
ALTER TABLE `table_name` ADD PRIMARY KEY (`column`);
联合索引
联合索引是同时在多个字段上创建索引,查询效率更高。
创建方式是这样的:
ALTER TABLE `table_name` ADD INDEX index_name (`column1`, `column2`, `column3`);
全文索引
全文索引主要用来匹配字符串文本中关键字。
当需要字符串中是否包含关键字的时候,我们一般用like,如果是以%开头的时候,则无法用到索引,这时候就可以使用全文索引了。
创建方式是这样的:
ALTER TABLE `table_name` ADD FULLTEXT (`column`);
索引和sql语句的优化
1、前导模糊查询不能使用索引,如name like ‘%静’
2、Union、in、or可以命中索引,建议使用in
3、负条件查询不能使用索引,可以优化为in查询,其中负条件有!=、<>、not in、not exists、not like等
4,联合索引最左前缀原则,又叫最左侧查询,如果在(a,b,c)三个字段上建立联合索引,那么它能够加快a|(a,b)|(a,b,c)三组的查询速度。
5、建立联合查询时,区分度最高的字段在最左边
6.如果建立了(a,b)联合索引,就不必再单独建立a索引。同理,如果建立了(a,b,c)索引就不必再建立a,(a,b)索引
7、存在非等号和等号混合判断条件时,在建索引时,要把等号条件的列前置
8、范围列可以用到索引,但是范围列后面的列无法用到索引。索引最多用于一个范围列,如果查询条件中有两个范围列则无法全用到索引。范围条件有:<、<=、>、>=、between等。
9、把计算放到业务层而不是数据库层。在字段上计算不能命中索引。
10、强制类型转换会全表扫描,如果phone字段是varcher类型,则下面的SQL不能命中索引。Select * fromuser where phone=13800001234
11、更新十分频繁、数据区分度不高的字段上不宜建立索引。
更新会变更B+树,更新频繁的字段建立索引会大大降低数据库性能。“性别”这种区分度不太大的属性,建立索引是没有什么意义的,不能有效过滤数据,性能与全表扫描类似。一般区分度在80%以上就可以建立索引。区分度可以使用count(distinct(列名))/count(*)来计算。
12、利用覆盖索引来进行查询操作,避免回表。被查询的列,数据能从索引中取得,而不是通过定位符row-locator再到row上获取,即“被查询列要被所建的索引覆盖”,这能够加速度查询。
什么是回表查询?
通俗的讲就是,如果索引的列在 select 所需获得的列中(因为在 mysql 中索引是根据索引列的值进行排序的,所以索引节点中存在该列中的部分值)或者根据一次索引查询就能获得记录就不需要回表,如果 select 所需获得列中有大量的非索引列,索引就需要到表中找到相应的列的信息,这就叫回表。
InnoDB聚集索引的叶子节点存储行记录,因此, InnoDB必须要有,且只有一个聚集索引:
(1)如果表定义了主键,则PK就是聚集索引;
(2)如果表没有定义主键,则第一个非空唯一索引(not NULL unique)列是聚集索引;
(3)否则,InnoDB会创建一个隐藏的row-id作为聚集索引;
先创建一张表,sql 语句如下:
create table xttblog(id int primary key, k int not null, name varchar(16),index (k)
)engine = InnoDB;
然后,我们再执行下面的 SQL 语句,插入几条测试数据。
INSERT INTO xttblog(id, k, name) VALUES(1, 2, 'xttblog'),(2, 1, '业余草'),(3, 3, '业余草公众号');
假设,现在我们要查询出 id 为 2 的数据。那么执行
select * from xttblog where ID = 2;
这条 SQL 语句就不需要回表。原因是根据主键的查询方式,则只需要搜索 ID 这棵 B+ 树。主键是唯一的,根据这个唯一的索引,MySQL 就能确定搜索的记录。
但当我们使用 k 这个索引来查询 k = 2 的记录时就要用到回表。
select * from xttblog where k = 2;
原因是通过 k 这个普通索引查询方式,则需要先搜索 k 索引树,然后得到主键 ID 的值为 1,再到 ID 索引树搜索一次。这个过程虽然用了索引,但实际上底层进行了两次索引查询,这个过程就称为回表。
也就是说,基于非主键索引的查询需要多扫描一棵索引树。因此,我们在应用中应该尽量使用主键查询。
什么是索引覆盖?
只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快。
explain的输出结果Extra字段为Using index时,能够触发索引覆盖。
如何实现索引覆盖?
1、常见的方法是:将被查询的字段,建立到联合索引里去。
例子
create table user (
id int primary key,
name varchar(20),
sex varchar(5),
index(name)
)engine=innodb;
第一个sql:
select id,name from user where name='shenjian';
能够命中name索引,索引叶子节点存储了主键id,通过name的索引树即可获取id和name,无需回表,符合索引覆盖,效率较高。
Extra:Using index。
第二个sql:
select id,name,sex from user where name='shenjian';
能够命中name索引,索引叶子节点存储了主键id,没有储存sex,sex字段必须回表查询才能获取到,不符合索引覆盖,需要再次通过id值扫描聚集索引获取sex字段,效率会降低。
Extra:Using index condition。
如果把(name)单列索引升级为联合索引(name, sex)就不同了。
create table user1 (
id int primary key,
name varchar(20),
sex varchar(5),
index(name, sex)
)engine=innodb;
可以看到:
select id,name ... where name='shenjian';
select id,name,sex ... where name='shenjian';
单列索升级为联合索引(name, sex)后,索引叶子节点存储了主键id,name,sex,都能够命中索引覆盖,无需回表。
画外音,Extra:Using index。
13、建立索引的列不能为null,使用not null约束及默认值
14, 利用延迟关联或者子查询优化超多分页场景,MySQL并不是跳过offset行,而是取offset+N行,然后放弃前offset行,返回N行,那当offset特别大的时候,效率非常低下,要么控制返回的总数,要么对超过特定阈值的页进行SQL改写。
11.spring的两大特性?
控制反转(IoC,Inversion of Control):
概念: 控制反转是Spring框架的核心概念,它指的是将对象的创建、组装和管理的控制权从应用程序代码中反转到Spring容器中.。在传统的开发模式中,程序员负责直接创建和管理对象,而在IoC中,这些任务由容器负责。
使用范围: IoC广泛应用于整个Spring框架,涵盖了Bean的创建、依赖注入、生命周期管理等方面。它使得开发者能够更专注于业务逻辑,而不用过多关心对象的创建和管理。
作用: 实现了松耦合、可维护、可测试的代码结构。
通过IoC容器,如ApplicationContext,实现了对象的自动装配和依赖注入,简化了组件之间的关系管理。
依赖注入DI,英文全称,Dependency Injection
依赖注入:由IoC容器动态地将某个对象所需要的外部资源(包括对象、资源、常量数据)注入到组件(Controller, Service等)之中。简单点说,就是IoC容器会把当前对象所需要的外部资源动态的注入给我们。
Spring依赖注入的方式主要有四个,
基于注解注入方式、set注入方式、构造器注入方式、静态工厂注入方式。
推荐使用基于注解注入方式,配置较少,比较方便。
面向切面编程(AOP,Aspect-Oriented Programming):
什么是AOP?
面向切面编程,作为面向对象的一种补充,将公共逻辑(事务管理、日志、缓存等)封装成切面,跟业务代码进行分离,可以减少系统的重复代码和降低模块之间的耦合度.。切面就是那些与业务无关,但所有业务模块都会调用的公共逻辑。
AOP有两种实现方式:静态代理和动态代理。
静态代理
静态代理:代理类在编译阶段生成,在编译阶段将通知织入Java字节码中,也称编译时增强。AspectJ使用的是静态代理。
缺点:代理对象需要与目标对象实现一样的接口,并且实现接口的方法,会有冗余代码。同时,一旦接口增加方法,目标对象与代理对象都要维护。
动态代理
动态代理:代理类在程序运行时创建,AOP框架不会去修改字节码,而是在内存中临时生成一个代理对象,在运行期间对业务方法进行增强,不会生成新类。
Spring的AOP实现原理其实很简单,就是通过动态代理实现的。如果我们为Spring的某个bean配置了切面,那么Spring在创建这个bean的时候,实际上创建的是这个bean的一个代理对象,我们后续对bean中方法的调用,实际上调用的是代理类重写的代理方法。而Spring的AOP使用了两种动态代理,分别是JDK的动态代理,以及CGLib的动态代理。
作者:程序员大彬
链接:https://juejin.cn/post/7202254039215472695
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
12.spring事务管理?
事务的特性(ACID)了解么?
原子性(Atomicity): 一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。
事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
一致性(Consistency): 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
隔离性(Isolation): 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
事务隔离分为不同级别,
未提交读(Read uncommitted)、
提交读(read committed)、
可重复读(repeatable read)和
串行化(Serializable)。MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)
持久性(Durability): 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
作者:JavaGuide
链接:https://juejin.cn/post/6844903608224333838
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
脏读、不可重复读、幻象读概念说明:
a.脏读:指当一个事务正字访问数据,并且对数据进行了修改,而这种数据还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
因为这个数据还没有提交那么另外一个事务读取到的这个数据我们称之为脏数据。依据脏数据所做的操作肯能是不正确的。
b.不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有执行结束,另外一个事务也访问该同一数据,那么在第一个事务中的两次读取数据之间,由于第二个事务的修改第一个事务两次读到的数据可能是不一样的,这样就发生了在一个事物内两次连续读到的数据是不一样的,这种情况被称为是不可重复读。
c.幻读:一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读(两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中)
(1)read uncommited:是最低的事务隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。
(2)read commited:保证一个事物提交后才能被另外一个事务读取。另外一个事务不能读取该事物未提交的数据。
(3)repeatable read:这种事务隔离级别可以防止脏读,不可重复读。但是可能会出现幻象读。它除了保证一个事务不能被另外一个事务读取未提交的数据之外还避免了以下情况产生(不可重复读)。
(4)serializable:这是花费最高代价但最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读之外,还避免了幻象读。
12.1Spring 支持两种方式的事务管理
编程式事务管理
通过 TransactionTemplate或者TransactionManager手动管理事务,实际应用中很少使用,但是对于你理解 Spring 事务管理原理有帮助。
使用TransactionTemplate 进行编程式事务管理的示例代码如下:
@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {transactionTemplate.execute(new TransactionCallbackWithoutResult() {@Overrideprotected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {try {// .... 业务代码} catch (Exception e){//回滚transactionStatus.setRollbackOnly();}}});
}
使用 TransactionManager 进行编程式事务管理的示例代码如下:
@Autowired
private PlatformTransactionManager transactionManager;public void testTransaction() {TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());try {// .... 业务代码transactionManager.commit(status);} catch (Exception e) {transactionManager.rollback(status);}
}
声明式事务管理
推荐使用(代码侵入性最小),实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)。
使用 @Transactional注解进行事务管理的示例代码如下:
@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void aMethod {//do somethingB b = new B();C c = new C();b.bMethod();c.cMethod();
}
作者:JavaGuide
链接:https://juejin.cn/post/6844903608224333838
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
12.2Spring 事务管理接口介绍
Spring 框架中,事务管理相关最重要的 3 个接口如下:
PlatformTransactionManager: (平台)事务管理器,Spring 事务策略的核心。
TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。
TransactionStatus: 事务运行状态。
我们可以把 PlatformTransactionManager 接口可以被看作是事务上层的管理者,而 TransactionDefinition 和 TransactionStatus 这两个接口可以看作是事务的描述。
PlatformTransactionManager 会根据 TransactionDefinition 的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理 ,而 TransactionStatus 接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。
PlatformTransactionManager:事务管理接口
Spring 并不直接管理事务,而是提供了多种事务管理器 。
Spring 事务管理器的接口是: PlatformTransactionManager 。通过这个接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager),Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
PlatformTransactionManager 接口的具体实现如下:
PlatformTransactionManager接口中定义了三个方法:
package org.springframework.transaction;import org.springframework.lang.Nullable;public interface PlatformTransactionManager {//获得事务TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;//提交事务void commit(TransactionStatus var1) throws TransactionException;//回滚事务void rollback(TransactionStatus var1) throws TransactionException;
}
作者:JavaGuide
链接:https://juejin.cn/post/6844903608224333838
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
13.数据库三范式
范式的作用
范式是我们设计数据库表时遵循的一种规范要求,主要有两个优点:
消除重复数据减少冗余数据,从而让数据库内的数据能划分的更合理,
让磁盘空间得到更有效利用的一种标准化标准;消除潜在的异常(插入异常,更新异常,删除异常)
这也是面试中经常会问到的“数据库三范式指的是什么?”,
很多小伙伴只知道原子性、唯一性、独立性
1NF: 原子性(存储的数据应该具有“不可再分性”),字段是最小的的单元不可再分,
2NF:唯一性 (消除非主键部分依赖联合主键中的部分字段),满足1NF,表中的字段必须完全依赖于全部主键而非部分主键,
3NF:独立性,消除传递依赖(非主键值不依赖于另一个非主键值),满足2NF,非主键外的所有字段必须互不依赖,