文章目录
- 事务
- 什么是事务?
- 隔离性中的不同隔离级别
- 事务实现的原理
- 隔离级别的实现原理(MVCC)
- MySQL中的锁机制
- SQL优化
事务
什么是事务?
事务就是逻辑上的一组操作,在同一个事务中,如果有多条sql语句执行,要么都执行,要么都不执行。
关系型数据库都有ACID的特点
A(原子性)、C(一致性)、I(隔离性)、D(持久性)
原子性:事务是一个不可分割的工作单位,事务的中间操作要么全部完成,要么全部不做,不可能停滞在中间环节。
隔离性:并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的
持久性:事务一旦提交,数据就持久保存在硬盘中
一致性:执行事务前后,数据保持一致,比如转账,不管成功与否,转账人与收账人总金额保持不变
只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。
隔离性中的不同隔离级别
查看隔离级别
SELECT @@global.transaction_isolation,@@transaction_isolation;
设置隔离级别
-- 设置左边的
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- 设置右边的
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
读未提交:一个事务可以读取到另一个事务未提交的数据,这会带来脏读(垃圾数据,因为A事务可能会回滚),幻读,不可重复读问题,将隔离级别改为读已提交,可以解决脏读问题。
读已提交:一个事务只能读取另一个事务已经提交的数据,其避免了脏读,但仍然存在不可重复读和幻读问题
不可重复度: A事务中对数据库进行了两次查询,在两次查询中,B事务修改了数据库中的数据,导致A事务中两次查询的数据不同,这就是不可重复度
可重复读:同一个事务中多次读取相同的数据返回的结果是一样的,可以避免脏读和不可重复读,但可能会导致幻读。
幻读: 可重复读隔离级别—>一个事务查询一个范围内的数据时,另一个并发事务向这个范围内添加了一个新的数据,当之间的事务再次查询这个范围的数据时,就会发现之前没有的记录,这就是幻读。
串行化:最高的隔离级别,事务串行执行,事务不存在并发执行,可以避免脏读、不可重复读和幻读,但是效率低下.
事务实现的原理
InnoDB存储引擎提供了两种事务日志,redolog(重做日志)和undolog(回滚日志),redolog用于保证事务持久性,undolog则是事务原子性和隔离性实现的。
原子性的实现: 当事务回滚时能撤销所有已执行的sql语句。这个操作依赖的时undolog日志,undolog属于逻辑日志,里面记录的都是执行过所有操作的相反操作,用于回滚后将已执行的sql语句进行撤销。
持久性的实现: redolog叫做重做日志,用于记录事务中所有的修改操作,包括修改前和修改后的数据,这样即使系统崩溃,这些修改也不会丢失,系统恢复后,MySQL可以读取日志,重新执行这些操作。
隔离级别的实现原理(MVCC)
多版本并发控制
Multi-Version Concurrent Control
,是MySQL提高性能的一种方式,就是配合undolog使事务可以并发执行。读未提交: 没有特殊的并发控制机制,读操作不会获取任何锁,在读取数据之前不会进行任何检查。
读已提交: 读已提交又称当前读,每次读的时候都会给版本链拍照,所以读到的数据都是最新的(已提交)。这是通过在读取数据时加上共享锁,然后在读取完成后立即释放锁来实现的。
可重复读: 使用行级锁或快照隔离,当事务开始之后,第一次读取会给版本拍照,下次读取直接从版本快照中直接读取,所以一个事务中读取到的数据是一直的。
每次执行修改操作时,MySQL不会直接修改原始数据,而是创建一个新的版本。这个新版本包含了修改后的数据,以及生成这个版本的事务ID。当一个事务需要读取数据时,MySQL会根据事务的隔离级别和事务ID,从版本链中选择一个合适的版本。这样,即使在并发执行多个事务的情况下,每个事务也都能看到一个一致的数据视图。
MySQL中的锁机制
MySQL中的锁主要分为两种:共享锁和排他锁,共享锁允许多个事务同时获取相同资源的读取访问权限,而排他锁则只允许一个事务获取资源的写入访问权限。
在InnoDB中隔离性时通过锁机制来实现的,MySQL支持行锁、间隙锁、表锁
行锁: 只给操作行加锁,如果两个事务执行的是同一行,那么就一个一个执行,不同行的话一起执行
间隙锁: 一般指的是范围区间,对某一区间加锁
表锁: 对整个表加锁,MyISAM默认支持表锁
共享锁: 又称读锁,如果事务在读数据时,不想让其他事务写,但是还让其他事务读,这时候就在语句后面添加共享锁,在语句后直接加lock in share mode
排他锁: 就是独占锁,写操作默认加排他锁,当我们读数据时,要求数据足够准确,可以给读操作加排他锁,可以在语句后面直接加for update
SQL优化
1. 查询SQL尽量不要使用select * ,而是使用具体字段
节省资源、减少网络开销。可能用到覆盖索引,减少回表,提高查询效率
2. 避免在where子句中使用or来连接条件
使用or可能会使索引失效,从而全表扫描;
对于or没有索引的salary这种情况,假设它走了id的索引,但是走到salary查询条件时,它还得全表扫描
3. 尽量使用数值替代字符串类型
例:主键优先使用int类型,性别、状态,使用1,0替代
因为引擎在处理查询和连接的时候会逐个比较字符串中的每一个字符;
对于数字型而言只需要比较一次就够了;
字符会降低查询和连接的性能,并会增加存储开销;
4. 使用varchar代替char
varchar变长字段按数据内容实际长度存储,可以节省存储空间;
char按声明大小存储,不足补空格;
其次对于查询来说,在一个相对较小的字段内抖索,效率更高;
5. 对查询进行优化,应尽量避免全表扫描,首先应考虑建立索引(针对查询多,增删少)
6. 应尽量避免索引失效
- 在where子句中对字段进行null值判断,否则将导致引擎放弃使用索引而进行全表扫描,例如:select id from t where num is null,可以在num上设置默认值0,确保表中num列没有null值
- 应尽量避免在where子句中使用or来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where num=10 or num=20
- in 和 not in也要慎用,否则会导致全表扫描,如:select id from t where num in(1,2,3),对于连续的数值,能用between就不要用in, select id from t where num between 1 and 3
- 模糊查询也将导致全表查询
- 应尽量避免在where子句中对字段进行函数操作,这将
7. inner join 、left join、right join,优先使用 inner join
三种连接如果结果相同,优先使用 inner join
inner join 内连接,只保留两张表中完全匹配的结果集;
left join 会返回左表所有的行,即使在右表中没有匹配的记录;
right join 会返回右表所有的行,即使在左表中没有匹配的记录;
8. 提高 group by 语句的效率
反例:先分组,再过滤
正例:先过滤,后分组
9. 清空表时优先使用 truncate
truncate table 比 delete 速度快,且使用的系统和事务日志资源少.
delete 语句每次删除一行,并在事务日志中为所删除的每行记录一项。truncate
table 通过释放存储表数据所用的数据页来删除数据.
10. 表连接不宜太多,索引不宜太多,一般 5 个以内
联的表个数越多,编译的时间和开销也就越大
每次关联内存中都生成一个临时表
应该把连接表拆开成较小的几个执行,可读性更高
11. 避免在索引列上使用内置函数
使用索引列上内置函数,索引失效。
12. 使用 explain 分析你 SQL 执行计划