一、MySQL基础
1、命令行连接:
sql
mysql -uroot -p123456 --连接数据库
update mysql.user set authentication_string=password('123456') where user='root' and Host='localhost'; --修改用户密码
--------------------------------
--所有的语句都使用;结尾
show databases; --查看所有的数据库
use school; --切换数据库
show tables; --查看数据库中所有的表
describe student; --显示数据库中所有表的信息
create database person character set utf8 collate utf8_general_ci; --创建一个数据库
exit; --退出连接
/*多行注释*/
2、常用查询sql语句
sql
show create database 数据库名 --显示创建数据库的语句
show create table 表名 --显示创建表的语句
desc 表名 --显示表结构
select version() --查询系统版本(函数)
select 100*3-1 as 计算结果 --用来计算(表达式)
select @@auto_increment_increment --查询自增的步长(变量)
3、Innodb和myisam区别
事务支持
- InnoDB :支持 ACID(原子性、一致性、隔离性、持久性)事务特性,提供
COMMIT
、ROLLBACK
和保存点(savepoint)功能,适合需要事务处理的场景(如金融系统、订单管理)。 - MyISAM:不支持事务,所有操作立即生效且无法回滚,适用于无事务需求的场景。
外键约束
- InnoDB:支持外键,确保数据的引用完整性,适合复杂关联的表结构。
- MyISAM:不支持外键,表间关联需通过应用层逻辑实现。
索引与存储结构
- InnoDB
- 使用聚集索引(Clustered Index),数据文件和索引文件合并存储,主键索引直接指向数据行。
- 辅助索引(非主键索引)包含主键值,查询时需二次查找。
- 表空间(tablespace)存储数据,支持自动扩展。
- MyISAM
- 索引和数据分离存储(.MYI 索引文件和.MYD 数据文件)。
- 索引不包含数据,仅指向数据行的物理位置。
- 表损坏后修复难度较高。
锁机制
- InnoDB:支持行级锁(row-level locking),并发写入性能更好,适合高并发场景。
- MyISAM:仅支持表级锁(table-level locking),写入时会锁定整张表,并发性能较差。
崩溃恢复
- InnoDB:有崩溃恢复机制(通过重做日志和撤销日志),确保数据一致性。
- MyISAM:无崩溃恢复能力,表损坏后可能丢失数据。
统计信息
- InnoDB:统计信息是近似值(基于采样),优化器可能选择非最优执行计划。
- MyISAM:统计信息精确(存储总行数等),查询优化更准确。
空间占用
- InnoDB:因存储额外信息(如事务日志、回滚段),通常占用空间更大。
- MyISAM :空间利用率较高,但不支持压缩表(需手动使用
myisampack
)。
适用场景
- InnoDB:适合写密集、需要事务和外键的场景(如电商、论坛)。
- MyISAM:适合读密集、无事务需求的场景(如日志记录、数据仓库)。
选择建议
- 优先使用 InnoDB:除非明确需要 MyISAM 的特定特性(如全文索引的早期版本支持)。
- MySQL 5.5 + 默认引擎:MySQL 5.5 及以后版本默认使用 InnoDB。
4、表结构变更语句
sql
alter table 表名 rename 新表名 --修改表名
alter table 表名 add 字段 类型 --添加字段
alter table 表名 modify 约束 --modify修改约束条件,change 字段重命名
drop table --drop删除表或字段
5、delete和truncate区别
特性 | DELETE | TRUNCATE |
---|---|---|
条件删除 | 支持 | 不支持(只能全量删除) |
事务回滚 | 支持 | 不支持 |
触发器 | 触发 AFTER DELETE |
不触发 |
自增主键 | 保留当前值 | 重置为初始值 |
锁级别 | 行级锁 / 表级锁 | 表级锁(锁时间短) |
性能 | 慢(逐行删除 + 日志) | 快(直接释放页) |
空间释放 | 不一定释放(依赖存储引擎) | 释放全部空间 |
选择建议
- 清空全量数据且无需恢复 :优先使用
TRUNCATE
。 - 需条件删除或回滚能力 :使用
DELETE
。 - 高并发场景 :避免在事务中使用
TRUNCATE
(表级锁可能阻塞其他操作)。
6、关联查询
操作 | 描述 |
---|---|
inner join | 如果表中至少有一个匹配,就返回行 |
left join | 会从左表中返回所有的值,即使右表中没有 |
right join | 会从右表中返回所有的值,即使左表中没有 |
自连接:自己和自己联接,把一张表拆分为两张一样的表
7、MySQL常用函数
sql
一、数学函数
ABS(x) 返回x的绝对值
BIN(x) 返回x的二进制(OCT返回八进制,HEX返回十六进制)
CEILING(x) 返回大于x的最小整数值
EXP(x) 返回值e(自然对数的底)的x次方
FLOOR(x) 返回小于x的最大整数值
GREATEST(x1,x2,...,xn)返回集合中最大的值
LEAST(x1,x2,...,xn) 返回集合中最小的值
LN(x) 返回x的自然对数
LOG(x,y)返回x的以y为底的对数
MOD(x,y) 返回x/y的模(余数)
PI()返回pi的值(圆周率)
RAND()返回0到1内的随机值,可以通过提供一个参数(种子)使RAND()随机数生成器生成一个指定的值。
ROUND(x,y)返回参数x的四舍五入的有y位小数的值
SIGN(x) 返回代表数字x的符号的值
SQRT(x) 返回一个数的平方根
TRUNCATE(x,y) 返回数字x截短为y位小数的结果
二、聚合函数(常用于GROUP BY从句的SELECT查询中)
AVG(col)返回指定列的平均值
COUNT(col)返回指定列中非NULL值的个数
MIN(col)返回指定列的最小值
MAX(col)返回指定列的最大值
SUM(col)返回指定列的所有值之和
GROUP_CONCAT(col) 返回由属于一组的列值连接组合而成的结果
三、字符串函数
ASCII(char)返回字符的ASCII码值
BIT_LENGTH(str)返回字符串的比特长度
CONCAT(s1,s2...,sn)将s1,s2...,sn连接成字符串
CONCAT_WS(sep,s1,s2...,sn)将s1,s2...,sn连接成字符串,并用sep字符间隔
INSERT(str,x,y,instr) 将字符串str从第x位置开始,y个字符长的子串替换为字符串instr,返回结果
FIND_IN_SET(str,list)分析逗号分隔的list列表,如果发现str,返回str在list中的位置
LCASE(str)或LOWER(str) 返回将字符串str中所有字符改变为小写后的结果
LEFT(str,x)返回字符串str中最左边的x个字符
LENGTH(s)返回字符串str中的字符数
LTRIM(str) 从字符串str中切掉开头的空格
POSITION(substr,str) 返回子串substr在字符串str中第一次出现的位置
QUOTE(str) 用反斜杠转义str中的单引号
REPEAT(str,srchstr,rplcstr)返回字符串str重复x次的结果
REVERSE(str) 返回颠倒字符串str的结果
RIGHT(str,x) 返回字符串str中最右边的x个字符
RTRIM(str) 返回字符串str尾部的空格
STRCMP(s1,s2)比较字符串s1和s2
TRIM(str)去除字符串首部和尾部的所有空格
UCASE(str)或UPPER(str) 返回将字符串str中所有字符转变为大写后的结果
四、日期和时间函数
CURDATE()或CURRENT_DATE() 返回当前的日期
CURTIME()或CURRENT_TIME() 返回当前的时间
DATE_ADD(date,INTERVAL int keyword)返回日期date加上间隔时间int的结果(int必须按照关键字进行格式化),如:SELECTDATE_ADD(CURRENT_DATE,INTERVAL 6 MONTH);
DATE_FORMAT(date,fmt) 依照指定的fmt格式格式化日期date值
DATE_SUB(date,INTERVAL int keyword)返回日期date加上间隔时间int的结果(int必须按照关键字进行格式化),如:SELECTDATE_SUB(CURRENT_DATE,INTERVAL 6 MONTH);
DAYOFWEEK(date) 返回date所代表的一星期中的第几天(1~7)
DAYOFMONTH(date) 返回date是一个月的第几天(1~31)
DAYOFYEAR(date) 返回date是一年的第几天(1~366)
DAYNAME(date) 返回date的星期名,如:SELECT DAYNAME(CURRENT_DATE);
FROM_UNIXTIME(ts,fmt) 根据指定的fmt格式,格式化UNIX时间戳ts
HOUR(time) 返回time的小时值(0~23)
MINUTE(time) 返回time的分钟值(0~59)
MONTH(date) 返回date的月份值(1~12)
MONTHNAME(date) 返回date的月份名,如:SELECT MONTHNAME(CURRENT_DATE);
NOW() 返回当前的日期和时间
QUARTER(date) 返回date在一年中的季度(1~4),如SELECT QUARTER(CURRENT_DATE);
WEEK(date) 返回日期date为一年中第几周(0~53)
YEAR(date) 返回日期date的年份(1000~9999)
8、事务
什么是事务?
要么都执行,要么都不执行
将一组sql语句放到一个批次中去执行
MySQL是默认开启事务自动提交的,关闭语句 set autocommit=0/1
事务的基本要素(ACID)
● 原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,
不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的
操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原
子,是物质构成的基本单位
● 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破
坏 。比如 A 向 B 转账,不可能 A 扣了钱,B 却没收到。
● 隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务
之间彼此没有任何干扰。比如 A 正在从一张银行卡中取钱,在 A 取钱的过程结束前,
B 不能向这张卡转账。
● 持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据
库,不能回滚。
MySQL事务隔离级别:
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read-uncommitted) | 是 | 是 | 是 |
读已提交(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
事务的并发问题
● 脏读:事务 A 读取了事务 B 更新的数据,然后 B 回滚操作,那么 A 读取到的
数据是脏数据
● 不可重复读:事务 A 多次读取同一数据,事务 B 在事务 A 多次读取的过程
中,对数据作了更新并提交,导致事务 A 多次读取同一数据时,结果 不一致
● 幻读:系统管理员 A 将数据库中所有学生的成绩从具体分数改为 ABCDE 等
级,但是系统管理员 B 就在这个时候插入了一条具体分数的记录,当系统管理员 A 改
结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
如何解决脏读、幻读、不可重复读
● 脏读: 隔离级别为 读提交、可重复读、串行化可以解决脏读
● 不可重复读:隔离级别为可重复读、串行化可以解决不可重复读
● 幻读:隔离级别为串行化可以解决幻读、通过 MVCC + 区间锁可以解决幻读
小结:
不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。
解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
手动处理事务
sql
set autocommit=0 --关闭自动提交
start transaction --标记一个事务的开始
sql语句
commit --提交:持久化(成功)
rollback --回滚:回到原来的样子(失败)
--事务接受
set autocommit=1 --开启自动提交
9、索引
-
主键索引(primary key):唯一的标识,主键不可重复,不能为空
-
唯一索引(unique key):索引列不可重复,可为空,可存在多个索引列
-
常规索引(key/index):默认的,index,key关键字来设置
-
组合索引(join index):多个字段联合创建索引,进一步提高查询效率
-
全文索引(full text):在特定的数据库引擎下才有,myisam,快速定位数据
基础语法:
sql--索引的使用1,在创建表时添加索引;2,在创建完毕后,增加索引 show index from table_name --显示所有索引信息 alter table table_name add fulltext index 索引名 (字段) explain sql语句 --分析sql执行的状况 create index 索引名 on 表(字段) --创建索引
索引原则:
- 索引不是越多越好
- 不要对经常变动数据加索引
- 小数据量的表不需要加索引
- 索引一般加在常用来查询的字段上
阅读:http://blog.codinglabs.org/articles/theory-of-mysql-index.html
10、用户权限管理
create user 用户名 identified by '密码'
set password=password('密码')
set password for 用户名 =password('密码')
rename user 用户名 to 新用户名
grant all privileges on *.* to 用户名 --给所有用户授予最高权限
show grant for 用户名 --查询指定用户权限
revoke all privileges on *.* from 用户名 --撤销权限
drop user 用户名 --删除用户
11、数据库设计规范
- 分析需求:分析业务和需要处理的数据库的需求
- 概要设计:设计关系图E-R图
三大范式:
第一范式(1NF):每一列都具有原子性,有唯一的业务含义,不可再分
第二范式(2NF):在第一范式的基础上,每张表中每一行数据所代表的业务含义都是唯一可区分的
第三范式(3NF):在第一二范式的基础上,需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关
为了解决规范性和性能问题,关联查询的表不得超过三张表:
-
考虑商业化的需求和目标,(成本,用户体验!)数据库的性能更加重要
-
在规范性能的问题的时候,需要适当的考虑一下规范性
-
故意给某些表增加一些冗余的字段,(从多表查询变为单表查询)
-
故意增加一些计算列(从大数据量降低为小数据量的查询:索引)
12、JDBC
Java
//1.加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");//固定写法
//2.url和用户信息
String url="jdbc:mydql://lacalhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true";
String username="root";
String password="123456";
//3.获取连接,数据库对象Connection代表数据库
Connection connection=DriverManager.getConnection(url,user.password);
//4.获取执行sql语句的对象Stament,执行SQL
Statement statement=connection.createStatement();
//5.执行SQL获取结果
String sql="select * from users";
Result result=statement.executeQuery(sql);
while(result.next()){
system.out.println("id="+result.getObject("id"));
system.out.println(============================));
}
//6.关闭连接
result.close();
statement.close();
connection.close();
13、数据库连接池
数据库连接--执行完毕--释放
连接--释放 十分浪费系统资源
池化技术:准备一些预先的资源,过来就了解预先准备好的
最小连接数:10
最大连接数:20
等待超时:100ms
编写连接池,实现一个接口DataSource
开源数据源实现
DBCP
C3P0
Druid
二、MySQL调优
MySQL架构设计
MySQL 架构可以分为 Server 层和 Engine层两部分;
架构图:

注意:MySQL8.0后移除了查询缓存功能。
InnoDB的逻辑存储结构

InnoDB中,所有的数据都被逻辑存放到一个空间中,称之为表空间(TableSpace),进一步细分为 段(Segment)、区(Extend)、页(Page)、行(Row)。
表空间(tablespace)
-
表空间是Innodb存储引擎逻辑的最高层,所有的数据都存放在表空间中。
-
默认情况下,Innodb存储引擎有一个共享表空间ibdata1,即所有数据都存放在这个表空间中内。
-
如果启用了innodb_file_per_table参数,需要注意的是每张表的表空间内存放的只是数据、索引、和插入缓冲Bitmap,其他类的数据,比如回滚(undo)信息、插入缓冲检索页、系统事物信息,二次写缓冲等还是放在原来的共享表内的。
段(segment)
-
表空间由段组成,常见的段有数据段、索引段、回滚段等。
-
InnoDB存储引擎表是索引组织的,因此数据即索引,索引即数据。数据段即为B+树的叶子结点,索引段即为B+树的非索引结点。
-
在InnoDB存储引擎中对段的管理都是由引擎自身所完成,DBA不能也没必要对其进行控制。
区(extent)
-
区是由连续页组成的空间,在任何情况下每个区的大小都为1MB。
-
为了保证区中页的连续性,InnoDB存储引擎一次从磁盘申请4~5个区。
-
默认情况下,InnoDB存储引擎页的大小为16KB,一个区中一共64个连续的页。
页(page)
- 页是InnoDB磁盘管理的最小单位。
- 在InnoDB存储引擎中,默认每个页的大小为16KB。
- 从InnoDB1.2.x版本开始,可以通过参数innodb_page_size将页的大小设置为4K,8K,16K。
- InnoDB存储引擎中,常见的页类型有:数据页,undo页,系统页,事务数据页,插入缓冲位图页,插入缓冲空闲列表页等。
Innodb页结构相关示意图

其中File Header、Page Header、File Trailer 的大小是固定的,分别为38,56,8 字节,这些空间用来标记该页的一些信息,如Checksum,数据页所在B+树索引的层数等。
User Records、Free Space、Page Directory这些部分为实际的行记录存储空间,因此大小是动态的。
下边我们用表格的方式来大致描述一下这7个部分:

每当我们插入一条记录,都会从Free Space 部分,也就是尚未使用的存储空间中申请一个记录大小的空间划分到User Records 部分,当Free Space 部分的空间全部被User Records部分替代掉之后,也就意味着这个页使用完了,如果还有新的记录插入的话,就需要去申请新的页了。
这个过程的图示如下:

每张表中可以有成千上万条记录,一个页只有16KB ,所以可能需要好多页来存放数据。不同页其实构成了一条双向链表 ,File Header 是InnoDB页的第一部分,它的FIL_PAGE_PREV 和FIL_PAGE_NEXT就分别代表本页的上一个和下一个页的页号,即链表的上一个以及下一个节点指针。

Innodb索引结构图
聚集索引结构图

- 聚集索引就是以主键创建的索引
- 聚集索引在叶子节点存储的是表中的数据
非聚集索引结构图

- 非聚集索引就是以非主键创建的索引
- 非聚集索引在叶子节点存储的是主键和索引列
- 使用非聚集索引查询出数据时,拿到叶子上的主键再去查到想要查找的数据。(拿到主键再查找这个过程叫做回表)
- 假设所查询的列,刚好都是索引对应的列,不用再回表 查,那么这个索引列,就叫覆盖索引。
InnoDB 锁类型

加锁机制
乐观锁与悲观锁是两种并发控制的思想,可用于解决丢失更新问题。
乐观锁
- 每次去取数据,都很乐观,觉得不会出现并发问题。
- 因此,访问、处理数据每次都不上锁。
- 但是在更新的时候,再根据版本号或时间戳判断是否有冲突,有则处理,无则提交事务。
悲观锁
- 每次去取数据,很悲观,都觉得会被别人修改,会有并发问题。
- 因此,访问、处理数据前就加排他锁。
- 在整个数据处理过程中锁定数据,事务提交或回滚后才释放锁.
锁粒度
- 表锁: 开销小,加锁快;锁定力度大,发生锁冲突概率高,并发度最低;不会出现死锁。
- 行锁: 开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高。
- 页锁: 开销和加锁速度介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般
兼容性
共享锁:
- 又称读锁(S锁)。
- 一个事务获取了共享锁,其他事务可以获取共享锁,不能获取排他锁,其他事务可以进行读操作,不能进行写操作。
- SELECT ... LOCK IN SHARE MODE 显示加共享锁。
排他锁:
- 又称写锁(X锁)。
- 如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
- SELECT ... FOR UPDATE 显示添加排他锁。
锁模式
- 记录锁: 在行相应的索引记录上的锁,锁定一个行记录
- gap锁: 是在索引记录间歇上的锁,锁定一个区间
- next-key锁: 是记录锁和在此索引记录之前的gap上的锁的结合,锁定行记录+区间。
- 意向锁 是为了支持多种粒度锁同时存在;
MySQL索引
索引数据结构
MySQL索引使⽤的数据结构主要有 BTree索引 和 hash索引
对于hash索引来说,底层的数据结构就是哈希表,因此在绝⼤多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余⼤部分场景建议选择BTree索引
性能优化的思路
-
⾸先需要使⽤慢查询功能,去获取所有查询时间⽐较⻓的SQL语句
-
其次使⽤explain命令去查询由问题的SQL的执⾏计划
-
最后可以使⽤show profile[s] 查看由问题的SQL的性能使⽤情况
-
优化SQL语句
慢查询参数
sql
show VARIABLES like '%slow_query%';

- slow_query_log:是否开启慢查询,on为开启,off为关闭;
- log-slow-queries:慢查询⽇志⽂件路径
sql
SHOW VARIABLES LIKE "%long_query_time%";

- long_query_time : 阈值,超过多少秒的查询就写⼊⽇志
sql
show variables like 'log_queries_not_using_indexes';

- 系统变量 log-queries-not-using-indexes :未使⽤索引的查询也被记录到慢查询⽇志中(可选项)。如果调优的话,建议开启这个选项
开启慢查询⽇志(临时)
在MySQL执⾏SQL语句设置,但是如果重启MySQL的话会失效。
sql
set global slow_query_log=on;
set global long_query_time=1;
开启慢查询⽇志(永久)
修改:/etc/my.cnf,添加以下内容,然后重启MySQL服务
sql
[mysqld]
lower_case_table_names=1
slow_query_log=ON
slow_query_log_file=D:\dev\mysql-8.0.22-winx64\data\DESKTOP-LEC7QQM-slow.log
long_query_time=1
(数据库操作超过100毫秒认为是慢查询,可根据需要进⾏设定,如果过多,可逐步设定,⽐如先⾏设定为2秒,逐渐降低来确认瓶颈所在
MySQL性能分析 EXPLAIN
通过explain命令可以得到:
- 表的读取顺序
- 数据读取操作的操作类型
- 哪些索引可以使⽤
- 哪些索引被实际使⽤
- 表之间的引⽤
- 每张表有多少⾏被优化器查询
使用一条简单的sql看看使用explain关键字的效果:
mysql
EXPLAIN SELECT * FROM `person`;
执行结果:

从上图中看到执行结果中会显示12列信息,每列具体信息如下:

EXPLAIN字段介绍
id列:
该列的值是select查询中的序号,比如:1,2,3,4等,它决定了表的执行顺序
某条sql的执行计划中的id一般会出现三种情况:
- id相同
- id不同
- id相同和不同的都有
id相同的情况
执行sql如下:
mysql
EXPLAIN SELECT * FROM person t1 INNER JOIN person t2 on t1.id = t2.id
结果:

执行顺序:id相同,执行顺序从上到下,先执行表t1,再执行表t2
id不同的情况
执行sql如下:
mysql
explain select * from person t1 where t1.id = (select id from person t2 where t2.id=2);
结果:

执行顺序:id不同,id序号大的先执行,这里会从下到上执行,先执行表t2,再执行表t1
id相同和不同的都有的情况
执行sql如下:
mysql
explain
select t1.* from person t1
inner join (select max(id) mid from person group by id) t2
on t1.id=t2.mid
结果:

执行顺序:id序号大的先执行,先执行person,id相同的从上到下执行 t1, 最后是派生表
select_type列
该列表示select的类型,具体包含了如下11种类型

但是常用的其实就是下面几个:
类型 | 含义 |
---|---|
SIMPLE | 简单SELECT查询,不包含子查询和UNION |
PRIMARY | 复杂查询中的最外层查询,表示主要的查询 |
SUBQUERY | SELECT或WHERE列表中包含了子查询 |
DERIVED | FROM列表中包含的子查询,即衍生 |
UNION | UNION关键字之后的查询 |
UNION RESULT | 从UNION后的表获取结果集 |
下面看看这些SELECT类型具体是怎么出现的:
1、SIMPLE
执行sql如下:
mysql
explain select * from person;
结果:

它只在简单SELECT查询中出现,不包含子查询和UNION
2、PRIMARY 和 SUBQUERY
执行sql如下:
mysql
explain select * from person t1 where t1.id = (select id from person t2 where t2.id=2);
结果:

我们看到这条嵌套查询的sql中,最外层的t1表是PRIMARY类型,而最里面的子查询t2表是SUBQUERY类型。
3、DERIVED
执行sql如下:
mysql
explain
select t1.* from person t1
inner join (select max(id) mid from person group by id) t2
on t1.id=t2.mid
结果:

最后一条记录就是衍生表,它一般是FROM列表中包含的子查询,这里是sql中的分组子查询。
4、UNION 和 UNION RESULT
执行sql如下:
mysql
explain
select * from person
union
select* from person
结果:

UNION 和 UNION RESULT一般会成对出现。
table列
该列的值表示输出行所引用的表的名称
但也可以是以下值之一:
<unionM,N>
:具有和id值的行的M并集N。<derivedN>
:用于与该行的派生表结果id的值N。派生表可能来自(例如)FROM子句中的子查询 。<subqueryN>
:子查询的结果,其id值为N
partitions列
该列的值表示查询的记录分区
type列
该列的值表示连接类型,是查看索引执行情况的一个重要指标。包含如下类型:

执行结果从最好到最坏的的顺序是从上到下。
我们需要重点掌握的是下面几种类型:
system > const > eq_ref > ref > range > index > ALL
下面逐一看看常见的几个连接类型是怎么出现的:
1、system
这种类型要求数据库表中只有一条数据,是const类型的一个特例,一般情况下是不会出现的。
2、const
通过一次索引就能找到数据,一般用于主键或唯一索引作为条件的查询sql中
3、eq_ref
常用于主键或唯一索引扫描
4、ref
常用于非主键和唯一索引扫描
5、range
常用于范围查询,比如:between ... and 或 In 等操作
6、index
全索引扫描。执行sql如下:
explain select id from person1
结果:

7、ALL
全表扫描。执行sql如下:
mysql
EXPLAIN SELECT * FROM person1 t1
结果:

possible_keys列
该列表示可能的索引选择。
请注意,此列完全独立于表的顺序,这就意味着possible_keys在实践中,某些键可能无法与生成的表顺序一起使用。
如果此列是NULL,则没有相关的索引。在这种情况下,您可以通过检查该WHERE 子句以检查它是否引用了某些适合索引的列,从而提高查询性能。
key列
该列表示实际用到的索引。
可能会出现possible_keys列为NULL,但是key不为NULL的情况。
key_len列
该列表示使用索引的长度。上面的key列可以看出有没有使用索引,key_len列则可以更进一步看出索引使用是否充分。不出意外的话,它是最重要的列。

有个关键的问题浮出水面:key_len是如何计算的?
决定key_len值的三个因素:
1.字符集
2.长度
3.是否为空
常用的字符编码占用字节数量如下:

目前我的数据库字符编码格式用的:UTF8占3个字节。
mysql常用字段占用字节数:
字段类型 | 占用字节数 |
---|---|
char(n) | n |
varchar(n) | n + 2 |
tinyint | 1 |
smallint | 2 |
int | 4 |
bigint | 8 |
date | 3 |
timestamp | 4 |
datetime | 8 |
此外,如果字段类型允许为空则加1个字节。
上图中的 783 是怎么算的?
783 = 260 * 3 + 2 + 1

还有一个问题:为什么这列表示索引使用是否充分呢,还有使用不充分的情况?
执行sql如下:
mysql
explain select id from person1 where username = 'stu001' and email='stu01@qq.com';
结果:

上图中使用了联合索引:idx_username_email,key_len为1566,刚好是两个字段的长度,所以是索引全匹配,索引使用充分
ref列
该列表示索引命中的列或者常量。
rows列
该列表示MySQL认为执行查询必须检查的行数,越少越好。
对于InnoDB表,此数字是估计值,可能并不总是准确的。
filtered列
该列表示按表条件过滤的表行的估计百分比。最大值为100,这表示未过滤行。值从100减小表示过滤量增加。
rows显示了检查的估计行数,rows× filtered显示了结果的行数。例如,如果 rows为1000且 filtered为50.00(50%),则结果的行数为1000×50%= 500。
Extra列
该字段包含有关MySQL如何解析查询的其他信息,这列还是挺重要的,但是里面包含的值太多,就不一一介绍了,只列举几个常见的。
1、Impossible WHERE
表示WHERE后面的条件一直都是false,
2、Using filesort
表示按文件排序,排序未使用到索引,性能极差,需要进行优化。
3、Using index
表示是否用了覆盖索引,说白了它表示是否所有获取的列都走了索引。
4、Using temporary
表示是否使用了临时表,一般多见于order by 和 group by语句,这类SQL语句性能较低,往往也需要进⾏优化。
5、Using where
表示使用了where条件过滤。
6、Using join buffer
表示是否使用连接缓冲。来自较早联接的表被部分读取到联接缓冲区中,然后从缓冲区中使用它们的行来与当前表执行联接。
重点关注的列
key(查看有没有使用索引)
key_len(查看索引使用是否充分)
type查看索引类型)
Extra(查看附加信息:排序、临时表、where条件为false等)
一般情况下根据这4列就能找到索引问题。
三、MySQL主从同步
1、什么是MySQL主从同步
MySQL主从同步是一种通过二进制日志文件进行分布式同步的核心技术,在数据库高可用、负载均衡等场景中应用广泛,原理和流程如下:
2、主从同步核心原理
1、进制日志文件记录
主库(Master)将所有数据的变更操作(增/删/改)以事件的形式记录到二进制(Binlog)中。
支持的复制类型:Satement(SQL语句)、Row(变更的行数据)、Mixed(混合模式语句+行数据)
默认采用基于语句的复制,一旦发现基于语句的复制无法实现精确复制时,就会采用基于行的复制
2、日志传输与中继
- 从库I/O线程:连接主库,请求Binlog事件,写入到本地的中继日志。
- 主库Log Dump线程:响应从库请求,推送Binlog事件。
3、日志重放
从库SQL线程:解析中继日志中的事件,在从库上顺序执行sql,事件数据同步。
3、核心应用场景
- 读写分离
- 主库负责处理写操作,从库承担读请求,降低主库压力,适用于高并发查询系统
- 数据备份与容灾
- 从库作为实时的备份库,当主库故障时可以快速切换到新的主库,减少数据丢失
- 负载均衡与扩展
- 通过添加多个从库水平扩展读能力,适用数据分析平台、报表系统等读密集型场景
- 特殊场景优化
- 延迟复制:从库故意延迟应用日志(如:防止主库误操作)
四、使用binlog恢复数据
1、确认二进制日志功能已经开启
查看my.cnf(或my.ini)配置文件,确保log_bin变量设置为非空值,或者使用以下SQL命令
mysql
SHOW VARIABLES LIKE 'log_bin';

2、查看二进制日志文件列表,确定从哪个日志文件开始恢复
mysql
SHOW BINARY LOGS;

3、解密二进制文件输出到指定binlog文件,方便定位需要恢复的位置区间
mysqlbinlog DESKTOP-UGO8QDK-bin.000022 -vv > test.binlog
4、使用mysqlbinlog工具来查看日志中的事件,找到对应的数据点位置区间,恢复数据
mysql
# gva: 对应的数据库
mysqlbinlog --start-position="2811" --stop-position="3039" DESKTOP-UGO8QDK-bin.000022 | mysql -u root -p gva
五、面试题
1、MySQL中MyIsam和Innodb两种存储引擎的区别?
-
事务支持:
- MyISAM强调的是性能,每次查询具有原子性,其执行速度比InnoDB更快,但是不支持事务支持。
- InnoDB支持事务。
-
MyISAM仅支持表锁,增删改查语句都会给表自动加锁。InnoDB支持行级锁,支持高并发读写操作。
-
InnoDB支持MVCC,MyISAM不支持。
-
InnoDB支持外键,MyISAM不支持。
-
InnoDB不支持全文索引,MyISAM支持。
-
可移植性、备份及恢复:
- MyISAM:数据以文件的形式存储,在跨平台的数据迁移中很方便。在备份和恢复方面可针对单个表操作。
- InnoDB:支持拷贝数据文件、备份binlog。或用mysqldump,但在数据量达到几十G时就相对痛苦了。
-
存储结构:
- MyISAM:所有的表拆分为3个文件分别存储 表结构、索引、数据。
- InnoDB:所有的表都保存在一个数据文件中,InnoDB表的大小只受限于操作系统的大小,一般为2GB。
2、MVCC(多版本并发控制)
MVCC是数据库管理系统用于实现高并发读写的核心技术。它通过为数据库保存多个历史版本,在保证事务隔离性的同时,避免了传统锁机制带来的大量阻塞,显著提升了数据库的并发性能。
核心原理
MVCC的核心思想是:每个事务读取的数据都是一个历史快照,而不是当前最新的数据。即使其它事务正在修改数据,当前事务也不会被阻塞,而是读取该数据在事务开始时的版本。
实现方式
- 数据多版本存储:数据库为每行数据保存多个版本,每个版本都带有时间戳或事务ID。
- 事务可见性规则:根据事务开始时间和其它事务的提交状态,决定当前事务可以看到哪个版本的数据。
与锁机制的对别
特性 | MVCC | 传统锁机制 |
---|---|---|
读写冲突 | 无冲突,不相互阻塞 | 读写互斥 |
并发性能 | 高 | 低 |
事务隔离 | 通过可见性规则实现 | 通过锁粒度(表锁/行锁)实现 |
3、MySQL 中InnoDB支持的四种事务隔离级别。
- Read Uncommitted(读未提交):每个事务都可以看到其它事务未提交的执行结果。会导致脏读。
- Read Commited(读已提交):每个事务都只能读取其它事务已经提交的内容。这是大多数数据库系统的默认隔离级别(但不是MySQL默认的),但不可重复读。
- Repeatable Read(可重复读):这是MySQL的默认隔离级别,它能保证一个事务中,多次读取数据时,会看到相同的数据,但在范围查找时存在幻读的情况。
- Serializable(可串行化):这是最高的隔离级别,它通过强制事务排序,实质不可能相互冲突,可以解决幻读的问题。
4、mysql 中varchar(50)、char(50) 、int(10) 各自代表的含义?
假设字符集编码是uft8mb4,每个字符占4个字节,不同字符集编码代表的含义不同。
- varchar(50):字段长度可变,节省空间、最大长度是50个字符,50*4=200个字节。
- char(50):字段长度固定50个字符,若填充内容不足50,则自动补充空格。
- int(10):数值的最大展示宽度。
5、把表中大字段拆分成子表的好处是什么?
如果表中有大字段(text、blob)类型的,而且这些字段的访问频率低,这时候把大字段和其它小字段放在一块就变成了缺点。因为MySQL数据库中记录是按行存储的,数据块大小又是固定的(16k),每条记录越小,相同的块存储的记录数就越多。若把大字段拆分为子表,那么就能提高查询效率。
6、MySQL中InnoDB引擎行级锁是加在什么上实现的?
InnoDB行锁是通过给索引项加锁来实现的,这也就意味着:只有通过索引条件检索时,InnoDB才使用行级锁,否则InnoDB将使用表锁。
7、MySQL 中控制内存分配的全局参数,有哪些?
-
nnodbbufferpool_size : 表示缓冲池字节大小,InnoDB 缓存表和索引数据的内存区域。mysql默认的值是128M。最大值与你的CPU体系结构有关,在32位操作系统,最大值是4294967295(2^32-1),在64位操作系统,最大值为18446744073709551615 (2^64-1)。> 在 32 位操作系统中,CPU 和操作系统实用的最大大小低于设置的最大值。如果设定的缓冲池的大小大于 1G,设置 innodbbufferpoolinstances的值大于1. > 数据读写在内存中非常快, innodbbufferpoolsize 减少了对磁盘的读写。 当数据提交或满足检查点条件后才一次性将内存数据刷新到磁盘中。然而内存还有操作系统或数据库其他进程使用, 一般设置 bufferpool 大小为总内存的 3/4 至 4/5。 若设置不当, 内存使用可能浪费或者使用过多。 对于繁忙的服务器, buffer pool 将划分为多个实例以提高系统并发性, 减少线程间读写缓存的争用。buffer pool 的大小首先受innodbbufferpool_instances 影响, 当然影响较小。
-
querycachesize > 当mysql接收到一条 select 类型的 query时,mysql 会对这条 query 进行 hash 计算而得到一个 hash 值,然后通过该 hash 值到 query cache 中去匹配,如果没有匹配中,则将这个hash 值存放在一个 hash 链表中,同时将 query 的结果集存放进cache 中,存放 hash 值的链表的每一个 hash 节点存放了相应 query结果集在 cache 中的地址,以及该 query 所涉及到的一些 table 的相关信息;如果通过 hash 值匹配到了一样的 query,则直接将 cache 中相应的 query 结果集返回给客户端。如果 mysql 任何一个表中的任何一条数据发生了变化,便会通知 query cache 需要与该 table 相关的query 的 cache 全部失效,并释放占用的内存地址。
query cache优缺点
- query 语句的 hash 计算和 hash 查找带来的资源消耗。mysql 会对每条接收到的 select 类型的 query 进行 hash 计算然后查找该 query 的 cache 是否存在,虽然 hash 计算和查找的效率已经足够高了,一条 query 所带来的消耗可以忽略,但一旦涉及到高并发,有成千上万条 query 时,hash 计算和查找所带来的开销就的重视了;
- query cache 的失效问题。如果表变更比较频繁,则会造成 query cache 的失效率非常高。表变更不仅仅指表中的数据发生变化,还包括结构或者索引的任何变化;
- 对于不同 sql 但同一结果集的 query 都会被缓存,这样便会造成内存资源的过渡消耗。sql 的字符大小写、空格或者注释的不同,缓存都是认为是不同的 sql(因为他们的 hash 值会不同);
- 相关参数设置不合理会造成大量内存碎片,相关的参数设置会稍后介绍。
-
readbuffersize >是 MySQL 读入缓冲区大小。对表进行顺序扫描的请求将分配一个读入缓冲区,MySQL 会为它分配一段内存缓冲区。readbuffersize 变量控制这一缓冲区的大小。如果对表的顺序扫描请求非常频繁,并且你认为频繁扫描进行得太慢,可以通过增加该变量值以及内存缓冲区大小提高其性能。
8、having 子句和 where 的异同点?
- 语法上:where 用于表中列名,having用于 select 结果列名
- 影响结果范围:where 从表中读取数据的行数,having 返回客户端的行数
- 索引:where 可以使用索引,having 不能使用索引,只能在临时结果集操作
- where 后面不能使用聚集函数,having 是专门使用聚集函数的
9、MySQL 当记录不存在插入,存在则更新,语句怎么写?
insert into table(a,b,c) values(1,2,3) on duplicate key update c=c+1;
10、Select语句完整的执行顺序
(1)from 子句组装来自不同数据源的数据;
(2)where 子句基于指定的条件对记录行进行筛选;
(3)group by 子句将数据划分为多个分组;
(4)使用聚集函数进行计算;
(5)使用 having 子句筛选分组;
(6)计算所有的表达式;
(7)select 的字段;
(8)使用 order by 对结果集进行排序。
11、B+tree与B-tree区别
B + 树和 B 树(B - 树)是两种常见的多路平衡查找树,广泛应用于数据库索引和文件系统中。它们在结构、查询效率、适用场景等方面有显著区别
结构差异
1. 节点存储内容
- B 树 :
每个节点既存储索引键 ,也存储对应的数据记录 (或数据地址)。
叶子节点和非叶子节点都可能包含数据,查询可能在非叶子节点就终止。 - B + 树 :
只有叶子节点 存储完整数据记录 (或数据地址),非叶子节点仅存储索引键 (用于导航)。
所有查询最终都必须到达叶子节点才能获取数据。
2. 叶子节点连接方式
- B 树:叶子节点之间是独立的,没有指针连接。
- B + 树 :所有叶子节点通过双向链表连接,形成一个有序的链表结构。
核心区别对比
对比维度 | B 树 | B + 树 |
---|---|---|
数据存储位置 | 所有节点(叶子 + 非叶子)都可能存数据 | 仅叶子节点存数据,非叶子节点仅存索引 |
查询效率 | 不稳定:可能在非叶子节点命中,也可能需要到叶子节点 | 稳定:必须遍历到叶子节点,查询路径长度固定 |
范围查询 | 效率低:需回溯父节点查找相邻数据 | 效率高:叶子节点链表可直接顺序遍历 |
磁盘 IO 次数 | 较多:非叶子节点存数据导致单节点索引键少,树高更高 | 较少:非叶子节点仅存索引,单节点可存更多键,树高更低 |
数据冗余 | 无冗余:索引键不重复 | 有冗余:非叶子节点的索引键是叶子节点的副本 |
删除操作 | 复杂:可能导致非叶子节点下溢 | 较简单:仅操作叶子节点,通过链表维护 |
适用场景
- B 树适用场景 :
适合随机查询 为主的场景(如内存数据库),因为可能在非叶子节点直接命中数据,减少查询深度。
例:MongoDB 的索引曾使用 B 树变体。 - B + 树适用场景 :
适合范围查询和顺序访问 (如关系型数据库索引),叶子节点的链表结构让范围查询(如WHERE id BETWEEN 10 AND 100
)效率极高。
例:MySQL 的 InnoDB 引擎主键索引采用 B + 树。
六、MySQL分库分表
分库分表时解决关系型数据库性能瓶颈的核心技术,尤其是在高并发、大数据量的互联网场景下至关重要。
1、核心价值
- 单库存在连接数限制(MySQL默认151连接)
- 单表数据量过大性能下降(超过500W行性能显著下降)
- 磁盘I/O存在吞吐瓶颈
2、典型的应用场景
分库分表 分库分表 分库分表 用户量大于1000W 电商交易系统 日订单>50W 支付系统 数据量>10TB 日志分析平台
3、技术实现模式
-
垂直拆分
分库:按业务模块拆分(用户库、订单库、商品库)
分表:将宽表拆分为多个子表
-
水平拆分
-
分库分表策略:
策略 实现方式 优点 缺点 范围法 按ID范围拆分 易于扩容 数据热点 哈希法 user_id%128 分布均匀 扩容复杂 一致性哈希 虚拟节点环 扩容影响小 实现复杂 -
分片键选择原则
- 高频查询字段(如user_id)
- 数据分布均匀
- 避免跨分片查询
-
4、核心挑战及解决方案
- 分布式事务 :
- Saga模式:补偿事务
- TCC模式:Try-Confirm-Cancel
- Seata框架:
java
@GlobalTransactional
public void crossShardOperation() {
// 操作分片1
// 操作分片2
}
- 跨分片查询:
java
// ShardingSphere 跨库查询
ShardingHintManager hintManager = ShardingHintManager.getInstance();
hintManager.addDatabaseShardingValue("user", 1);
hintManager.addDatabaseShardingValue("user", 2);
// 执行查询(自动合并结果)
List<User> users = userMapper.selectAll();
- 全局ID生成:
方案 | 实现复杂度 | 性能 | 趋势递增性 |
---|---|---|---|
Snowflake | ★★☆ | 100万+/秒 | 是 |
Leaf-Segment | ★☆☆ | 10万+/秒 | 是 |
Redis INCR | ★★☆ | 50万+/秒 | 否 |
5、最佳实践
-
分库分表不是银弹:
- 单表<500万行不必分表
- 优先考虑读写分离
- 使用缓存降低DB压力
-
扩容方案设计:
数据增长 初始16分片 双倍扩容 停写服务 数据迁移 修改路由规则 恢复服务
- 推荐:使用一致性哈希减少迁移量
-
监控关键指标:
- 分片负载均衡率
- 跨分片查询比例
- 慢查询分布
- 连接池利用率
-
技术选型
- ShardingSphere vs MyCat
- 自研方案 vs 开源方案
- 云原生方案(如PolarDB-X)
希法 | user_id%128 | 分布均匀 | 扩容复杂 |
| 一致性哈希 | 虚拟节点环 | 扩容影响小 | 实现复杂 |
-
分片键选择原则
- 高频查询字段(如user_id)
- 数据分布均匀
- 避免跨分片查询
4、核心挑战及解决方案
- 分布式事务 :
- Saga模式:补偿事务
- TCC模式:Try-Confirm-Cancel
- Seata框架:
java
@GlobalTransactional
public void crossShardOperation() {
// 操作分片1
// 操作分片2
}
- 跨分片查询:
java
// ShardingSphere 跨库查询
ShardingHintManager hintManager = ShardingHintManager.getInstance();
hintManager.addDatabaseShardingValue("user", 1);
hintManager.addDatabaseShardingValue("user", 2);
// 执行查询(自动合并结果)
List<User> users = userMapper.selectAll();
- 全局ID生成:
方案 | 实现复杂度 | 性能 | 趋势递增性 |
---|---|---|---|
Snowflake | ★★☆ | 100万+/秒 | 是 |
Leaf-Segment | ★☆☆ | 10万+/秒 | 是 |
Redis INCR | ★★☆ | 50万+/秒 | 否 |
5、最佳实践
-
分库分表不是银弹:
- 单表<500万行不必分表
- 优先考虑读写分离
- 使用缓存降低DB压力
-
扩容方案设计:
数据增长 初始16分片 双倍扩容 停写服务 数据迁移 修改路由规则 恢复服务
- 推荐:使用一致性哈希减少迁移量
-
监控关键指标:
- 分片负载均衡率
- 跨分片查询比例
- 慢查询分布
- 连接池利用率
-
技术选型
- ShardingSphere vs MyCat
- 自研方案 vs 开源方案
- 云原生方案(如PolarDB-X)
总结:分库分表是Java高级开发者必须掌握的分布式系统核心能力。它不仅要求理解数据分片原理,更需要具备在业务场景中平衡性能、复杂度、扩展性的架构设计能力。实际工作中,我会优先考虑业务需求,避免过度设计,在必要时采用成熟中间件方案平稳落地。