2023年3月14日 22:34 周二转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!
本文来自公号 MySQL 技术,JavaGuide 对其做了补充完善。原文地址:https://mp.weixin.qq.com/s/d5OowNLtXBGEAbT31sSH4g
优化 SQL 的第一步应该是读懂 SQL 的执行计划。本篇文章,我们一起来学习下 MySQL EXPLAIN
执行计划相关知识。
什么是执行计划?
#
执行计划 是指一条 SQL 语句在经过 MySQL 查询优化器 的优化会后,具体的执行方式。
执行计划通常用于 SQL 性能分析、优化等场景。通过 EXPLAIN
的结果,可以了解到如数据表的查询顺序、数据查询操作的操作类型、哪些索引可以被命中、哪些索引实际会命中、每个数据表有多少行记录被查询等信息。
如何获取执行计划?
#
-- 提交准备数据
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for dept_emp
-- ----------------------------
DROP TABLE IF EXISTS `dept_emp`;
CREATE TABLE `dept_emp` (
`id` int(0) NOT NULL,
`emp_no` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`other1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`other2` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `index_emp_no`(`emp_no`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of dept_emp
-- ----------------------------
INSERT INTO `dept_emp` VALUES (1, 'a1', 'o11', '012');
INSERT INTO `dept_emp` VALUES (2, 'a2', 'o21', 'o22');
INSERT INTO `dept_emp` VALUES (3, 'a3', 'o31', 'o32');
INSERT INTO `dept_emp` VALUES (4, 'a4', 'o41', 'o42');
INSERT INTO `dept_emp` VALUES (5, 'a5', 'o51', 'o52');
SET FOREIGN_KEY_CHECKS = 1;
MySQL 为我们提供了 EXPLAIN
命令,来获取执行计划的相关信息。
...
2023年3月12日 17:53 周日转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!
题目来源于:
牛客题霸 - SQL 必知必会
检索数据
#
select
用于从数据库中查询数据。
从 Customers 表中检索所有的 ID
#
现有表 Customers
如下:
编写 SQL 语句,从 Customers
表中检索所有的 cust_id
。
答案:
select cust_id
from Customers;
检索并列出已订购产品的清单
#
表 OrderItems
含有非空的列 prod_id
代表商品 id,包含了所有已订购的商品(有些已被订购多次)。
编写 SQL 语句,检索并列出所有已订购商品(prod_id
)的去重后的清单。
答案:
select distinct prod_id
from OrderItems;
知识点:distinct
用于返回列中的唯一不同值。
检索所有列
#
现在有 Customers
表(表中含有列 cust_id
代表客户 id,cust_name
代表客户姓名)
...
2023年3月12日 17:52 周日转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!
本文整理完善自下面这两份资料:
基本概念
#
数据库术语
#
数据库(database)
- 保存有组织的数据的容器(通常是一个文件或一组文件)。数据表(table)
- 某种特定类型数据的结构化清单。模式(schema)
- 关于数据库和表的布局及特性的信息。模式定义了数据在表中如何存储,包含存储什么样的数据,数据如何分解,各部分信息如何命名等信息。数据库和表都有模式。列(column)
- 表中的一个字段。所有表都是由一个或多个列组成的。行(row)
- 表中的一个记录。主键(primary key)
- 一列(或一组列),其值能够唯一标识表中每一行。
SQL 语法
#
SQL(Structured Query Language),标准 SQL 由 ANSI 标准委员会管理,从而称为 ANSI SQL。各个 DBMS 都有自己的实现,如 PL/SQL、Transact-SQL 等。
SQL 语法结构
#
![img](img/ly-20241212141857484.jpg)
SQL 语法结构包括:
子句
- 是语句和查询的组成成分。(在某些情况下,这些都是可选的。)表达式
- 可以产生任何标量值,或由列和行的数据库表谓词
- 给需要评估的 SQL 三值逻辑(3VL)(true/false/unknown)或布尔真值指定条件,并限制语句和查询的效果,或改变程序流程。查询
- 基于特定条件检索数据。这是 SQL 的一个重要组成部分。语句
- 可以持久地影响纲要和数据,也可以控制数据库事务、程序流程、连接、会话或诊断。
SQL 语法要点
#
- SQL 语句不区分大小写,但是数据库表名、列名和值是否区分,依赖于具体的 DBMS 以及配置。例如:
SELECT
与 select
、Select
是相同的。 - 多条 SQL 语句必须以分号(
;
)分隔。 - 处理 SQL 语句时,所有空格都被忽略。
SQL 语句可以写成一行,也可以分写为多行。
...
2023年2月27日 22:11 周一转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!
主从复制原理
![主从复制原理](img/ly-20241212141920958.png)
哨兵模式(简单)
![ly-20241212141921249](img/ly-20241212141921249.png)
哨兵模式详解
先配置主从模式,再配置哨兵模式
所有的哨兵 sentinel.conf 都是配置为监听master–> 192.168.14.101,如果主机宕机,sentinel.conf 中的配置也会自动更改为选举后的![ly-20241212141921408](img/ly-20241212141921408.png)
Java客户端连接原理
客户端是和Sentinel来进行交互的,通过Sentinel来获取真正的Redis节点信息,然后来操作.实际工作时,Sentinel 内部维护了一个主题队列,用来保存Redis的节点信息,并实时更新,客户端订阅了这个主题,然后实时的去获取这个队列的Redis节点信息.
/**
代码相对比较简单
**/
//1.设置sentinel 各个节点集合
Set<String> sentinelSet = new HashSet<>();
sentinelSet.add("192.168.14.101:26379");
sentinelSet.add("192.168.14.102:26380");
sentinelSet.add("192.168.14.103:26381");
//2.设置jedispool 连接池配置文件
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(10);
config.setMaxWaitMillis(1000);
//3.设置mastername,sentinelNode集合,配置文件,Redis登录密码
JedisSentinelPool jedisSentinelPool = new JedisSentinelPool("mymaster",sentinelSet,config,"123");
Jedis jedis = null;
try {
jedis = jedisSentinelPool.getResource();
//获取Redis中key=hello的值
String value = jedis.get("hello");
System.out.println(value);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(jedis != null){
jedis.close();
}
}
![ly-20241212141921579](img/ly-20241212141921579.png)
...
2023年2月24日 09:31 周五转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!
看到很多小伙伴简历上写了“熟练使用缓存”,但是被我问到“缓存常用的3种读写策略”的时候却一脸懵逼。
在我看来,造成这个问题的原因是我们在学习 Redis 的时候,可能只是简单了写一些 Demo,并没有去关注缓存的读写策略,或者说压根不知道这回事。
但是,搞懂3种常见的缓存读写策略对于实际工作中使用缓存以及面试中被问到缓存都是非常有帮助的!
下面介绍到的三种模式各有优劣,不存在最佳模式,根据具体的业务场景选择适合自己的缓存读写模式。
Cache Aside Pattern(旁路缓存模式)
#
Cache Aside Pattern 是我们平时使用比较多的一个缓存读写模式,比较适合读请求比较多的场景。
Cache Aside Pattern 中服务端需要同时维系 db 和 cache,并且是以 db 的结果为准。
下面我们来看一下这个策略模式下的缓存读写步骤。
写 :
简单画了一张图帮助大家理解写的步骤。
![img](img/ly-20241212141916648.jpg)
读 :
- 从 cache 中读取数据,读取到就直接返回
- cache 中读取不到的话,就从 db 中读取数据返回
- 再把数据放到 cache 中。
简单画了一张图帮助大家理解读的步骤。
![ly-20241212141916935](img/ly-20241212141916935.png)
你仅仅了解了上面这些内容的话是远远不够的,我们还要搞懂其中的原理。
比如说面试官很可能会追问:“在写数据的过程中,可以先删除 cache ,后更新 db 么?”
答案: 那肯定是不行的!因为这样可能会造成 数据库(db)和缓存(Cache)数据不一致的问题。
举例:请求 1 先写数据 A,请求 2 随后读数据 A 的话,就很有可能产生数据不一致性的问题。
这个过程可以简单描述为:
请求 1 先把 cache 中的 A 数据删除 -> 请求 2 从 db 中读取数据【此时请求2把脏数据(对于请求1来说是)更新到缓存去了】->请求 1 再把 db 中的 A 数据更新,即请求1的操作非原子
...
2023年2月24日 09:26 周五转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!
什么是内存碎片?
#
你可以将内存碎片简单地理解为那些不可用的空闲内存。
举个例子:操作系统为你分配了 32 字节的连续内存空间,而你存储数据实际只需要使用 24 字节内存空间,那这多余出来的 8 字节内存空间如果后续没办法再被分配存储其他数据的话,就可以被称为内存碎片。
![ly-20241212141917438](img/ly-20241212141917438.png)
Redis 内存碎片虽然不会影响 Redis 性能,但是会增加内存消耗。
为什么会有 Redis 内存碎片?
#
Redis 内存碎片产生比较常见的 2 个原因:
1、Redis 存储存储数据的时候向操作系统申请的内存空间可能会大于数据实际需要的存储空间。
以下是这段 Redis 官方的原话:
To store user keys, Redis allocates at most as much memory as the maxmemory
setting enables (however there are small extra allocations possible).
Redis 使用 zmalloc
方法(Redis 自己实现的内存分配方法)进行内存分配的时候,除了要分配 size
大小的内存之外,还会多分配 PREFIX_SIZE
大小的内存。
zmalloc
方法源码如下(源码地址:https://github.com/antirez/redis-tools/blob/master/zmalloc.c):
void *zmalloc(size_t size) {
// 分配指定大小的内存
void *ptr = malloc(size+PREFIX_SIZE);
if (!ptr) zmalloc_oom_handler(size);
#ifdef HAVE_MALLOC_SIZE
update_zmalloc_stat_alloc(zmalloc_size(ptr));
return ptr;
#else
*((size_t*)ptr) = size;
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
return (char*)ptr+PREFIX_SIZE;
#endif
}
另外,Redis 可以使用多种内存分配器来分配内存( libc、jemalloc、tcmalloc),默认使用
jemalloc,而 jemalloc 按照一系列固定的大小(8 字节、16 字节、32 字节……)来分配内存的。jemalloc 划分的内存单元如下图所示:
...
2023年2月23日 23:02 周四转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!
除了 5 种基本的数据结构之外,Redis 还支持 3 种特殊的数据结构 :Bitmap、HyperLogLog、GEO。
Bitmap
#
介绍
#
Bitmap 存储的是连续的二进制数字(0 和 1),通过 Bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte,所以 Bitmap 本身会极大的节省储存空间。
你可以将 Bitmap 看作是一个存储二进制数字(0 和 1)的数组,数组中每个元素的下标叫做 offset(偏移量)。
![img](https://camo.githubusercontent.com/1c1ce8351556fe9727cadfd385fdbd6f449d92a174bbd65db5383791fec05a13/68747470733a2f2f67756964652d626c6f672d696d616765732e6f73732d636e2d7368656e7a68656e2e616c6979756e63732e636f6d2f6769746875622f6a61766167756964652f64617461626173652f72656469732f696d6167652d32303232303732303139343135343133332e706e67)
常用命令
#
命令 | 介绍 |
---|
SETBIT key offset value | 设置指定 offset 位置的值 |
GETBIT key offset | 获取指定 offset 位置的值 |
BITCOUNT key start end | 获取 start 和 end 之前值为 1 的元素个数 |
BITOP operation destkey key1 key2 … | 对一个或多个 Bitmap 进行运算,可用运算符有 AND, OR, XOR 以及 NOT |
Bitmap 基本操作演示 :
...
2023年2月23日 14:22 周四转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!
Redis 共有 5 种基本数据结构:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。
这 5 种数据结构是直接提供给用户使用的,是数据的保存形式,其底层实现主要依赖这 8 种数据结构:简单动态字符串(SDS)、LinkedList(双向链表)、Hash Table(哈希表)、SkipList(跳跃表)、Intset(整数集合)、ZipList(压缩列表)、QuickList(快速列表)。
Redis 基本数据结构的底层数据结构实现如下:
String | List | Hash | Set | Zset |
---|
SDS | LinkedList/ZipList/QuickList | Hash Table、ZipList | ZipList、Intset | ZipList、SkipList |
Redis 3.2 之前,List 底层实现是 LinkedList 或者 ZipList。 Redis 3.2 之后,引入了 LinkedList 和 ZipList 的结合 QuickList,List 的底层实现变为 QuickList。
你可以在 Redis 官网上找到 Redis 数据结构非常详细的介绍:
未来随着 Redis 新版本的发布,可能会有新的数据结构出现,通过查阅 Redis 官网对应的介绍,你总能获取到最靠谱的信息。
![ly-20241212141914862](img/ly-20241212141914862.png)
String(字符串)
#
介绍
#
String 是 Redis 中最简单同时也是最常用的一个数据结构。
String 是一种二进制安全的数据结构,可以用来存储任何类型的数据比如字符串、整数、浮点数、图片(图片的 base64 编码或者解码或者图片的路径)、序列化后的对象。
...
2023年2月23日 11:16 周四转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!
Redis 事务
#
如何使用 Redis 事务?
#
Redis 可以通过 MULTI
,EXEC
,DISCARD
和 WATCH
等命令来实现事务(transaction)功能。
> MULTI
OK
> SET PROJECT "JavaGuide"
QUEUED
> GET PROJECT
QUEUED
> EXEC
1) OK
2) "JavaGuide"
MULTI
命令后可以输入多个命令,Redis 不会立即执行这些命令,而是将它们放到队列,当调用了
EXEC
命令后,再执行所有的命令。
这个过程是这样的:
- 开始事务(
MULTI
); - 命令入队(批量操作 Redis 的命令,先进先出(FIFO)的顺序执行);
- 执行事务(
EXEC
)。
你也可以通过
DISCARD
命令取消一个事务,它会清空事务队列中保存的所有命令。
> MULTI
OK
> SET PROJECT "JavaGuide"
QUEUED
> GET PROJECT
QUEUED
> DISCARD
OK
你可以通过
WATCH
命令监听指定的 Key,当调用 EXEC
命令执行事务时,如果一个被 WATCH
命令监视的 Key 被 其他客户端/Session 修改的话,整个事务都不会被执行。
...
2023年2月22日 11:31 周三转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!
Redis 基础
#
什么是 Redis?
#
Redis 是一个基于 C 语言开发的开源数据库(BSD 许可),与传统数据库不同的是 Redis 的数据是存在内存中的(内存数据库),读写速度非常快,被广泛应用于缓存方向。并且,Redis 存储的是 KV 键值对数据。
为了满足不同的业务场景,Redis 内置了多种数据类型实现(比如 String、Hash、【List、Set、】Sorted Set、Bitmap)。并且,Redis 还支持事务 、持久化、Lua 脚本、多种开箱即用的集群方案(Redis Sentinel、Redis Cluster)。
Redis 没有外部依赖,Linux 和 OS X 是 Redis 开发和测试最多的两个操作系统,官方推荐生产环境使用 Linux 部署 Redis。
个人学习的话,你可以自己本机安装 Redis 或者通过 Redis 官网提供的
在线 Redis 环境来实际体验 Redis。
![ly-20241212141918257](img/ly-20241212141918257.png)
全世界有非常多的网站使用到了 Redis ,
techstacks.io 专门维护了一个
使用 Redis 的热门站点列表 ,感兴趣的话可以看看。
Redis 为什么这么快?
#
Redis 内部做了非常多的性能优化,比较重要的主要有下面 3 点:
- Redis 基于内存,内存的访问速度是磁盘的上千倍;
- Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用(Redis 线程模式后面会详细介绍到);
- Redis 内置了多种优化过后的数据结构实现,性能非常高。
下面这张图片总结的挺不错的,分享一下,出自
Why is Redis so fast? 。
...