当前位置: 首页 > news >正文

MySql进阶学习

MySql架构

1、连接层

        在mysql服务中,负责客户端连接,进行身份认证,授权

2、服务层

        在服务层可以进行sql的分析、优化、逻辑的处理等等。

3、引擎层

        引擎层就是实际负责数据存储和提取操作的,mysql提供了不同的引擎(处理方式),可以根据需求进行选择。

4、物理文件存储层

        实际的文件存储,包括存储数据的文件,还有各种的日志文件

MySQL引擎

        引擎就是数据中的重点。引擎就是实际负责数据存储和提取操作的一种实现方式,不同的引擎处理方式是不同的,MySQL提供了多种引擎,下面主要介绍两种:

1、InnoDB

        支持事务(安全可靠的),支持行级锁(锁的粒度小,并发量高),支持外键约束,支持数据缓存,提高查询的效率,不存储总行数( select count(*) from table),聚簇索引,支持奔溃恢复,提供事务日志(red log和undo log)确保数据一致性,意外宕机可自动恢复,数据安全性高。

适用场景:需要事务,外键,高并发读写,数据安全需求高的。

2、MyISAM

        不支持事务,不支持外键,不支持行级锁,支持表锁,支持全文索引,存储表的总行数,非聚簇索引,不支持奔溃恢复,发生意外时,需要手动恢复(如使用 check table 和 repair table 命令)

适合场景:只读或者是读多写少,需要全文索引或者count快速统计。

索引

概念

        索引是一种有序的数据结构,可以帮助MySQL快速查找到数据。如果数据库中的数据量非常大,那么逐页、逐行查询,效率就低。索引类似于属的目录,可以帮助我们快速定位到具体的页数。

优势:

1、通过索引可以快速的定位到数据,降低数据的IO成本。

2、由于索引是排好序的,减少了排序的成本。

缺点:

1、索引信息也是需要占用空间的。

2、当我们的数据发生改变时(新增、删除),索引信息也是需要发生改变的

总而言之,索引虽好,但是不可以乱用。

索引分类

主键索引:设定为主键后数据库会自动建立索引 

ALTER TABLE 表名 add PRIMARY KEY 表名(列名);
删除建主键索引:
ALTER TABLE 表名 drop PRIMARY KEY ;

唯一索引:索引列的值必须唯一,可以为null

CREATE UNIQUE INDEX 索引名 ON 表名(列名);
删除索引
DROP INDEX 索引名 ON 表名;

 单列索引:一个索引只包含一个列,一个表中可以有多个单列索引

创建单值索引
CREATE INDEX 索引名 ON 表名(列名);
删除索引:
DROP INDEX 索引名;

 组合索引:又叫复合索引,即一个索引包含多个列,在数据库操作期间,复合索引比单列索引开更小(相对于多个列场景索引),当表的行数远大于索引列的数目时可以使用复合索引

创建复合索引
CREATE INDEX 索引名 ON 表名(列 1,列 2...);
删除索引:
DROP INDEX 索引名 ON 表名;

 组合索引最左前缀原则:列表中a b c 三列,为a b创建索引,那么使用时需要满足最左侧索引原则,在使用组合索引的列作为条件时,必须要出现最左侧列为条件,否则组合索引就会失效

例如:

select * from table where a='' and b= ''  -- 索引生效
select * from table where b='' and a= ''  -- 索引生效
select * from table where a='' and c= ''  -- 索引生效
select * from table where c='' and b= ''  -- 索引不生效

 前缀索引:当字段类型为字符串(varchar ,text等),有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO,影响查询效率。此时可以只将字符串的一部分前缀建立索引,这样就可以大大节约索引空间,从而提高索引效率

create index idx_xxxx on table_name(column(length))

全文索引:需要模糊查询时,一般索引无效,这时候就可以使用全文索引了 仅支持CHARVARCHARTEXT类型的字段

CREATE FULLTEXT INDEX 索引名 ON 表名(字段名) WITH PARSER ngram;
SELECT 结果 FROM 表名 WHERE MATCH(列名) AGAINST(‘搜索词')

查看索引:

SHOW INDEX FROM 表名;

创建索引的原则

1、那些情况下需要创建索引?

1、主键自动建立唯一索引

2、作为查询条件的列

3、尽量使用联合索引(几个列添加一个索引),减少单列索引

4、针对于数据量较大,且查询频繁的表

5、查询排序的字段、分组的中的字段,若通过索引去访问将大大提高排序速度

2、哪些情况不要创建索引?

1、表中的数据少

2、经常增删改的表,提高了查询速度,同时却会降低更新表的速度,因为在改变表时不仅要保存数据,还有修改索引文件。

3、where 条件里用不到的字段不创建索引。

4、数据重复且分布平均的字段,某个数据列包含许多重复的内容,建立索引没有太大的实际效果

 3、索引失效的情况

1、索引列参与计算或函数操作

因为索引存储的是字段原始值的有序结构,计算或函数会改变值的原始顺序,导致索引失效。

-- 索引列被函数处理
SELECT * FROM users WHERE YEAR(created_at) = 2023;  -- 失效-- 索引列参与计算
SELECT * FROM orders WHERE amount * 0.8 > 1000;      -- 失效

2、隐式类型转换

MySQL在比较不同类型的值时会进行隐式转换(如varchar转int),相当于对索引列使用了函数

-- 字段类型为VARCHAR,但查询时传入数值
SELECT * FROM products WHERE id = 123;  -- 若id为VARCHAR类型,会触发隐式转换,导致索引失效

3、模糊查询以通配符开头

B-Tree索引只能优化前缀匹配,当通配符%在开头时,无法确定搜索范围,导致全表扫描

SELECT * FROM users WHERE name LIKE '%张';  -- 失效(%在开头)

4、组合索引不满足最左匹配原则

5、OR条件分割索引列

当OR条件的多个字段中存在至少一个为建立索引的字段时,mysql会放弃使用索引(全表扫描更快)

SELECT * FROM users WHERE id = 1 OR name = '张三';  -- 可能失效

 

-- 确保OR条件的所有字段都有索引
CREATE INDEX idx_id_name ON users(id, name);  -- 多列索引-- 或拆分为UNION(适用于索引字段不同的场景)
SELECT * FROM users WHERE id = 1
UNION ALL
SELECT * FROM users WHERE name = '张三';

6、索引字段使用is null 或is not null 

部分数据库对null值的索引优化较差,可能导致全表扫描。

SELECT * FROM products WHERE category_id IS NULL;  -- 可能失效
-- 确保字段有索引且数据库版本支持NULL值索引优化(MySQL 5.7+对IS NULL支持较好)
CREATE INDEX idx_category ON products(category_id);-- 若IS NOT NULL频繁使用,可考虑用一个特殊值(如0)代替NULL

7、索引统计信息过期或索引碎片

示例:表数据频繁更新后,查询计划可能选择全表扫描而非索引。

原因:mysql依赖统计信息(如索引基数)生成查询计划,若统计信息过期或索引碎片化,会导致优化器误判。

解决方案:

-- 更新统计信息
ANALYZE TABLE users;-- 重建索引(减少碎片)
ALTER TABLE users ENGINE=InnoDB;  -- 重建整张表

8、查询条件使用!=或<>

SELECT * FROM products WHERE price != 100;  -- 可能失效

不等于操作符需要扫描索引的多个区间,可能导致优化器选择全表扫描。

解决方案:

-- 改用范围查询
SELECT * FROM products WHERE price < 100 OR price > 100;-- 若数据分布不均(如大部分值为100),可考虑索引

9、覆盖索引不满足

假设存在索引(a,b);

SELECT a, b FROM tbl WHERE a = 1;  -- 覆盖索引(仅查询索引列)
SELECT a, b, c FROM tbl WHERE a = 1;  -- 非覆盖索引(需回表查询c列)

原因:当查询字段超出索引范围时,需要回表获取数据,可能导致优化器放弃索引

解决方案:

-- 创建包含查询字段的覆盖索引
CREATE INDEX idx_ab_c ON tbl(a, b, c);  -- 包含c列,避免回表

10、索引选择性过低

SELECT * FROM users WHERE gender = '男';  -- 若gender只有两个值,索引选择性低

原因:当索引列的重复值过多(如。性别、状态字段),优化器可能认为全表扫描更高效。

解决方案:

-- 对于选择性低的字段,避免单独建索引
-- 若需加速查询,可与其他字段组合(如组合索引(gender, age))

索引数据结构

索引结构使用的是B+Tree,首先b+tree也是有序的,而且一个节点可以存储多个数据,非叶子节点只存储索引数据,索引每个节点可以存储跟多的索引数据。

数据存储在叶子节点,叶子节点之间还有指针指向。

聚簇索引和非聚簇索引

聚簇索引:找到了索引就找到了数据,那么就是聚簇索引,mysql中的innodb引擎中的主键索引就是聚簇索引。

1、数据和索引都存储在一个文件中。

2、主键索引树是直接与数据绑定的。

非聚簇索引:找到了索引但是还没有找到数据,需要再次回表查询,才能找到数据。myisam引擎就是非聚簇索引,索引和数据存在不同的文件中。

数据库事务

什么是事务?

        数据库事务就是对一次数据库操作过程的管理,保证一次与数据库交互的过程中执行的多条sql要么全部执行要么都不执行,保证原子性。

事务的特征

原子性:保证一次的操作中的多条sql在没有问题时,提交事务,多条sql都执行,一旦有问题,事务回滚,回滚到事务开始前的状态。

隔离性:数据库为提高读写的并发性,提供4中隔离级别:1、读 未提交、2、读 已提交、3、可重复读、4、串行化(一个一个来)

持久性:事务一旦提交后,就会保证数据的持久化

一致性:是事务的终极目标,以上三点都是为了保证一致性,当多个事务同时对一条数据多次操作时,最终结构与我们预期结构一致。

四种隔离级别

1、读 未提交:A事务可以读取到B事务还未提交的数据,并发最高,也是最不安全的

会出现:脏读、幻读、不可重复读等问题。

脏读:A事务修改了数据还没有提交,这时B事务读到了数据,但是A事务可能出差回滚了,这种情况下B事务读到的数据就是垃圾数据。

2、读 已提交:A事务能读到B事务提交的数据。

解决了脏读的问题,幻读和不可重复读没有解决。

不可重复读问题:同一个事物中读取多次同一个数据(ru id=5的数据),两次读取到的结果是不一致的。

因为在同一个事物中第一次读取到的数据是原来的数据,第二次读取时,数据被另一个事务修改了,所以读取到的数据和第一次的数据是不同的。

3、可重复读:同一个事务中,多次读取数据返回的结果都是一致的。

解决了脏读、不可重复读的问题,会出现幻读的问题。

幻读:同一个事务多次读取到的数据行数是不同的

普通的查询操作解决了幻读问题,但是在查询语句后+for update排他锁,就会出现幻读的问题

mysql默认的隔离级别是可重复读。

4、串行化:当一个事务操作时,其他事务是不可以执行的,即使是查询操作也不可以。相当于是加了一个锁

它解决了脏读、不可重复读、幻读等问题。

 隔离级别实现原理

持久性实现:使用到redo log日志文件(重做日志),保证提交事务的数据持久保存,当事务提交后,先用redo log日志文件进行存储,因为在此过程中可能会出现宕机,如果宕机了,确保操作数据存储记录下来(日志文件中),当服务恢复后,可以继续将日志中的数据写入到物理一个盘上。

原子性实现:使用到undo log日志文件,当我们执行insert语句时,undo log会记录insert语句也会记录一个反向操作delete语句,执行delete语句,当发生事务回滚时,执行undo log日志文件,执行反向操作。

隔离性实现:MVCC机制(多版本并发控制),每一次事务对数据操作时,都会记录一个历史记录,在每一行数据后都会记录(当前事务id,上一次事务id)

 

对该记录每次更新后,都会将旧值放到一条 undolog 中,就算是该记录 的一个旧版本,随着更新次数的增多,所有的版本都会被 db_roll_ptr 属性连接成一个链表,我们把这个链表称之为版本链,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务 id,这个信息很重要。
ReadView(读视图):是快照读 SQL 执行时 MVCC 提取数据的依据,记录并维护系统当前活跃的事务(未提交事务)id。
当前隔离级别是 读  已提交时:在一个事务中,每次读时,都会从一个历史版本记录中获取一个最新的快照,这样就导致每次读到的数据可能时不一样的,也就 造成不可重复读的问题
当前隔离级别是 可重复读时:在一个事务中,第一次读时,获得一个快照,后面再次读时,依旧是第一次获得的快照,所以每次读取的数据是一致的,也就 解决不可重复读的问题

一致性实现:以上三个都满足即可实现一致性。

锁机制

        锁机制保证了进行数据操作时,保证写操作安全可靠。

锁按照粒度划分

全局锁:对整个数据库实例加锁,加锁后整个实例就处于只读状态,已经更新操作的事务提交语句都将被阻塞。

如使用场景: 对数据库备份时锁住整个库,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。

表级锁:对当前操作的整张表加锁,实现简单,开销少,MyIsan和innodb都支持表级锁,myisam不支持行级锁,并发量低。

表锁分为两类:表共享锁、表排他锁

表共享锁:允许多个事务同时获取同一表的共享锁,互不阻塞,通常用于只读操作,防止其他事务对表结构进行修改。与其他共享锁兼容,与表排他锁互斥

应用场景:多个事务同时获取同一表的数据,且不允许表结构修改

-- 手动获取表共享锁(InnoDB引擎需显式声明)
LOCK TABLES users READ;-- 多个事务可同时执行读操作
SELECT * FROM users;-- 释放锁
UNLOCK TABLES;

表排他锁:同一时间仅允许一个事务获取排他锁,其他事务需要等待锁释放。用于对表结构进行修改或数据更新。与任何类型的锁都互斥

应用场景:对表结构进行修改,执行数据操作,需要防止其他事务的干扰。

-- 手动获取表排他锁
LOCK TABLES users WRITE;-- 执行写操作(如更新、删除)
DELETE FROM users WHERE status = 'inactive';-- 释放锁
UNLOCK TABLES;

行级锁:分为行锁、间隙锁、临建锁

行锁:就是只锁住一行的数据。行锁分为排他锁和共享锁

间隙锁:对某个范围内进行加锁如 id>1 and id <10对此区间加锁。

临建锁:行锁和间隙锁组合,在锁住数据的同时,在锁着数据前面的间隙。

是innodb在可重复读隔离级别下的核心锁机制,用于防止幻读(确保同一事物中多次读取同一范围数据时,不会出现新的记录)

-- 事务1
BEGIN;
SELECT * FROM users WHERE age BETWEEN 20 AND 30 FOR UPDATE;  -- 临键锁锁定[20, 30]-- 事务2(被阻塞)
INSERT INTO users (age) VALUES (25);  -- 无法插入,因为间隙(20, 30]被锁定

排他锁:增删改操作时,默认加的就是排他锁,锁住操作的那行数据,又称写锁。

查询语句默认是不加任何锁的,如果需要加排他锁,则在查询语句后+for update

共享锁:读锁,一般用于查询语句来加的,如果事务1给行1添加了共享锁,那么其他事务只能给行添加共享锁,就不能添加排他锁,

查询语句后面加 lock in share mode 

sql优化

1、查询sql尽量不要使用select * ,而是具体字段

2、尽量使用数值代替字符串类型

        如 性别 0表示男,1表示女

因为引擎在处理查询和连接时会逐个比较字符串中每一个字符的,而对于数字型只需要比较一次就够了。字符会降低查询和连接的性能,并会增加存储开销。

3、varchar代替char

varcahr变长字段按数据实际长度存储,可见节省存储空间,二char是按照声明的固定长度存储,对于查询来说,在一个相对较小的字段内搜索,效率更高。

4、对查询进行优化,建立索引,避免全表扫描。

5、尽量避免索引失效的查询条件

6、提高group by语句的效率

反例:先分组,后过滤

SELECT user_id, SUM(amount) FROM orders
GROUP BY user_id
HAVING city = '北京';

正例:先过滤,在分组

SELECT user_id, SUM(amount) FROM orders
where city='北京'
GROUP BY user_id

7、清空表时优先使用truncate

truncate比delete速度块,且使用的斯特和事物日志资源少,delete语句每次删除一行,并在事物日志中为所删除的每行记录一项,truncate table 通过释放存储表数据所用的数据页来删除数据

8、关联的表不宜够多,索引不宜过多

9、深度分页问题优化

反例:

select id,name from account limit 100000,10;

正例:

select id,name from account where id > 100000 order by id limit 10;

10、使用explain分析sql执行计划

id:选择标识符
select_type:表示查询的类型。
table:输出结果集的表
type:表示表的连接类型
possible_keys:表示查询时,可能使用的索引
key:表示实际使用的索引
key_len:索引字段的长度
rows:扫描出的行数(估算的行数)
extra:执行查询时的一些额外信息,这些信息有助于理解查询的执行计划和优化
数据库性能。

相关文章:

  • PHP8.0版本导出excel失败
  • 长三角、珠三角、成渝、京津冀四大城市群的区域与分布
  • ubuntu安装google chrome
  • 如何在 Windows 10 或 11 上通过命令行安装 Node.js 和 NPM
  • 06、基础入门-SpringBoot-依赖管理特性
  • golang中的反射示例
  • Java二叉树题目练习
  • 项目QT+ffmpeg+rtsp(二)——海康威视相机测试
  • Rust 学习笔记:关于 HashMap 的练习题
  • PostGIS实现栅格数据入库-raster2pgsql
  • [Java][Leetcode simple] 13. 罗马数字转整数
  • SLAM定位常用地图对比示例
  • 系分论文《论系统需求分析方法及应用》
  • Redis深度解析:高性能内存数据库的核心原理与应用实践
  • Rhino 8 犀牛保姆级安装教程
  • 常见的实时通信技术(轮询、sse、websocket、webhooks)
  • 从辅助到协作:GitHub Copilot的进化之路
  • Vue 3.0中响应式依赖和更新
  • 天拓四方锂电池卷绕机 PLC 物联网解决方案
  • Maven 插件扩展点与自定义生命周期
  • 特朗普公开“怼”库克:苹果不应在印度生产手机
  • 四川内江警方通报一起持刀伤人致死案:因车辆停放引起,嫌犯被抓获
  • 从《让·桑特伊》到《追忆》,假故事的胜利
  • 杭州“放大招”支持足球发展:足球人才可评“高层次人才”
  • 河南信阳拟发文严控预售许可条件:新出让土地开发的商品房一律现房销售
  • 佩斯科夫:若普京认为必要,将公布土耳其谈判俄方代表人选