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))
全文索引:需要模糊查询时,一般索引无效,这时候就可以使用全文索引了 仅支持CHAR、
VARCHAR
、TEXT
类型的字段
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)
一致性实现:以上三个都满足即可实现一致性。
锁机制
锁机制保证了进行数据操作时,保证写操作安全可靠。
锁按照粒度划分
全局锁:对整个数据库实例加锁,加锁后整个实例就处于只读状态,已经更新操作的事务提交语句都将被阻塞。
如使用场景: 对数据库备份时锁住整个库,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。
表级锁:对当前操作的整张表加锁,实现简单,开销少,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执行计划