Mysql进阶
Mysql体系结构
大体来说,MySQL 可以分为 Server 层和存储引擎层两部分。
Server层
主要包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内
置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
其中Server层又可以划分以下:
连接层:主要负责连接处理、身份验证、安全性等,一般 C/S 架构都会有这一层。 核心服务层:主要有查询缓存、分析器、优化器、执行器等,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。 |
存储引擎层
存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引
擎。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。也就是说如果我们在
create table时不指定表的存储引擎类型,默认会给你设置存储引擎为InnoDB。
mysql系统服务之外又包含数据存储层
数据存储层:文件系统存储数据,存放数据文件、日志文件等,完成与存储引擎的交互。
Server层的不同部件作用
解析器
SQL |
优化器
SQL |
执行器
SQL |
一条语句在Mysql底层执行过程
Buffer pool
在MySQL中,Buffer Pool是InnoDB存储引擎用于缓存数据和索引的区域。对于MySQL的InnoDB存储引擎来说,所有的读操作(查询)和写操作(增删改)都会首先被应用在Buffer Pool中,这是一个在内存里的缓存区域。其主要目标是减少对硬盘的读写操作,因为硬盘I/O是数据库操作的瓶颈之一。
当进行数据查询时,InnoDB存储引擎会首先在Buffer Pool中查找数据。如果在Buffer Pool中找到了数据(被称为缓存命中),那么查询结果就会直接被返回;如果没有找到(被称为缓存未命中),那么数据会从磁盘被加载到Buffer Pool中,然后再返回结果。
对于数据的修改操作,InnoDB先在Buffer Pool中修改数据,然后在修改数据的同时,将修改操作写入到redo log(重做日志)里。这时候该操作就算完成了,然后在适当的时机(比如Buffer Pool不够用,或者系统空闲的时候),再将Buffer Pool中的数据刷回到磁盘。
Buffer Pool的大小是可以配置的,通常我们希望将其设定为能够在物理内存中持有尽可能多的常用数据的大小。在64位的系统中,Buffer Pool的大小可以配置得非常大,只受限于服务器的物理内存大小。
另外,MySQL通过使用一种称为LRU(Least Recently Used)的算法来管理Buffer Pool中的数据。当Buffer Pool满了,需要为新的数据提供空间时,最近最少使用的页(也就是在最久的一段时间内都未被访问的页)会被清理出Pool。
总的来说,Buffer Pool是InnoDB提高读操作性能的重要机制之一。通过提供内存级别的数据访问,可以显著减少更慢的硬盘读写操作。
MySQL数据库的增删改查操作是直接作用在Buffer Pool上的。但需要注意的是,当Buffer Pool中的空间不足,或MySQL实例关闭,或操作系统重启等情况时,Buffer Pool中的数据会被刷写回磁盘,以确保数据的持久性。 |
查询语句
SQL |
更新操作
update更新操作,具体步骤如下: 1:加载数据库存储在磁盘的数据记录到缓存池 2:将旧数据记录到undo日志 3:更新的数据记录刷新缓存池数据 4:将新数据记录写到redo 日志 5:redo日志顺序写磁盘保存 开始准备提交事务 6:bin log日志记录操作日志 7: 写入commit标记到redo log 提交事务 8: 异步刷新缓存池更新记录到磁盘 |
Mysql事务
事务主要用于处理操作量大、复杂度高的数据。事务是保证数据一致性的重要机制,并且能在数据库错误甚至系统崩溃时保护数据不受损害。
ACID特性
1.原子性(Atomicity)
事务被视为一个不可分割的最小工作单元,整个事务中的所有操作,要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作。
2. 一致性(Consistency
事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致性与业务规则有关,比如一个人转账给另一个人,那么这个过程中两个人的总金额是不变的。
3. 隔离性(Isolation)
多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
4. 持久性(Durability)
已被提交的事务对数据库的修改是永久的,该修改即使在数据库系统崩溃后也将保持。
不同地方的使用事务操作的语法可能会有些许不同,但基本的SQL指令包括START TRANSACTION(或BEGIN)、COMMIT和ROLLBACK。
如何保证ACID
保证原子性:
InnoDB存储引擎通过“Undo日志”来保证事务的原子性。当一个事务开始时,InnoDB会引入一个新的Undo日志记录,其中记录了该事务涉及的数据的原始状态。如果事务在执行过程中出现错误,或者用户手动回滚该事务,InnoDB就会使用Undo日志,将发生变动的数据恢复到原始状态,从而达到回滚事务的目的。 |
如:
delete一条数据的时候,就会记录这条数据的曾经的信息,回滚的时候,insert这条旧数据
update一条数据的时候,就会记录之前的旧值,回滚的时候,根据旧值执行update操作
insert一条数据的时候,就会这条记录的主键,回滚的时候,根据主键执行delete操作
保证一致性:
InnoDB存储引擎通过redo log保证一致性,它用于记录对数据库执行的修改操作,如果系统出现崩溃,重做日志将用于恢复系统 |
保证隔离性:
在MySQL中,InnoDB存储引擎利用多版本并发控制(MVCC,Multiversion Concurrency Control)和锁定系统来实现事务的隔离性。
1. 多版本并发控制(MVCC):这是一种策略,使得每个用户可看到一个数据库的快照,而不是当前正在被其他用户更改的数据内容。这个快照是由执行读操作的事务的开始时间来定义的,因此该读取操作(或者整个事务)会看到在开始执行记录读取操作时存在的数据的布局。
2. 锁定机制:InnoDB还实现了一个强大的锁定机制以确保数据在并发情况下的一致性。 锁定机制包括共享锁(Shared locks)、排他锁(Exclusive locks)和意向锁(Intention locks)。锁定级别可以是行级和表级。行级锁为最小的锁定级别,它使得多个事务能够同时处理同一表中的不同行,从而达到很高的并发性。表级锁在整个表上锁定,适合全表操作(如删除表的所有行)。
3. 四大隔离级别:InnoDB根据SQL标准定义了四种不同的隔离级别:未提交读(READ UNCOMMITTED)、已提交读(READ COMMITTED)、可重复读(REPEATABLE READ)和串行化(SERIALIZABLE)。这四个级别通过控制在同一时间是否可以访问某一资源、是否阻塞以及何时解锁,实现了不同程度的隔离。
因此,所谓的隔离性,就是使并发的事务互不干扰,每个事务都感觉不到系统中其他事务的存在,尽管它们可能正在对相同的数据进行操作。
在事务隔离级别为读已提交(Read Commited)时,一个事务能够读到另一个事务已经提交的数据,是不满足隔离性的。但是当事务隔离级别为可重复读(Repeateable Read)中,是满足隔离性的 |
保证持久性:
在MySQL的InnoDB存储引擎中,事务的持久性(Durability)是通过其日志系统来实现的。该系统包含两种日志类型:重做日志(Redo Logs)和Undo日志。
1. 重做日志(Redo Logs):重做日志是InnoDB在提交事务前用来确保持久性的一种手段。重做日志是一种物理日志,记录的是对于页的物理修改。当一个事务开始时,InnoDB会首先在重做日志中记录下该事务要执行的所有修改操作(这个动作称为日志预写,write-ahead logging, WAL)。只有当所有的重做日志都已经被成功写入磁盘,事务才算提交成功。因此,即使在事务提交后立即发生系统崩溜,数据库系统也能通过重做日志恢复到最后的一致性状态。
2. Undo日志:Undo日志主要用于在事务失败或者被中断时回滚(撤销)已经进行的修改,确保数据库的一致性。由于Undo日志能够记录数据的原始状态,因此在事务提交前如果发生错误,MySQL也能够通过Undo日志将数据恢复到事务开始前的状态。
此外,为了确保数据的持久性,在日志写入磁盘时,InnoDB会使用一种称为“刷盘”(flush)的操作,确保日志真实地写入磁盘而非留在缓存中。如果数据仅存储在缓存中,系统崩溃可能会导致数据丢失。
因此,通过InnoDB存储引擎的日志系统以及刷盘操作, MySQL能够有效地保证事务持久性,确保一旦事务提交,其数据的改变就是永久的,即使在系统崩溃的情况下也能恢复到最后的一致性状态。
隔离机制/不同隔离级别带来问题如何解决
“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。
更新丢失(Lost Update)或脏写 "脏写"指的是一个事务修改了数据,但还未提交,另一个事务又对这些未提交的修改进行了覆盖。这就是脏写(Dirty Write) 脏写的主要问题是,它可能会破坏数据的完整性,给恢复和并发控制带来困难。为了避免脏写,关系型数据库管理系统(RDBMS)通常会使用写锁(Write Lock):在一个事务对数据进行修改(写操作)时,其他的任何事务都不能修改这个数据,必须等待直到第一个事务提交,这样就避免了脏写。 脏读(Dirty Reads) 所谓的"脏读"(Dirty Read),是指在数据库进行并发访问时,一个事务(事务A)读取了另一个正在执行且尚未提交的事务(事务B)中的数据。如果事务B由于某些原因回滚了,那么事务A读取到的数据将是无效的。 一句话:事务A读取到了事务B已经修改但尚未提交的数据,还在这个数据基础上做了操作。此时,如果B事务回滚,A读取的数据无效,不符合一致性要求。 举个例子,假设一个银行转账操作,事务A是你向朋友转账100元,转账操作是一个事务,包含两个步骤,先从你的账户扣款,后给朋友加款。假设在这两个步骤之间,你的账户已经被扣款100元,但是朋友的账户还未加款,这时候事务B读取了你朋友的账号数据,这就是脏读,因为事务B读取了尚未提交的事务数据。 为了避免脏读,数据库会提供多级别的隔离机制,如"可重复读"或"串行化"等级别,在这些隔离级别下,数据库会锁定正在使用的数据,防止其他事务在数据提交前进行访问。 不可重复读(Non-Repeatable Read)是指在一个事务内,多次读取同一数据,在这个事务还未结束时,其它事务也在访问该同一数据,那么,在第一个事务中,多次读取同一数据时,可能会得到不一样的结果。这种在一个事务内数据的非重复现象,就被称为“不可重复读”。 幻读(Phantom Read)是指在一个事务内部,执行了两次相同的查询,但由于另一个并发事务的插入或删除操作,第二次查询结果多了或少了记录,这就是所谓的“幻读”。 |
MySQL 锁
排它锁
排它锁(Exclusive Locks,也被称为写锁):当一个事务对数据进行修改(如 UPDATE,DELETE)操作时,会对数据加上排它锁。其他事务不能对此行数据进行读取或者修改操作。也就是说,如果一个事务持有了排它锁,在此锁释放前,其他任何事务都无法获取这个数据行的任何锁。
SELECT ... FOR UPDATE:这条语句对查询返回的所有行添加排他锁(写锁)。当你希望对查询的数据进行更新操作,并且希望确保这些数据在读取和修改之间不会被其他事务更改时,可以使用这个语句。在加了排他锁的行上,其他的事务不能加共享读锁或者排他写锁,即不能读取也不能修改这些行。 |
共享锁
共享锁(Shared Locks,也被称为读锁):当一个事务需要读取一条数据,并且不希望这条数据在读操作过程中被修改,那么可以对这条数据加上共享锁。一条数据上如果有共享锁,其他事务可以继续对这条数据加共享锁,进行读取操作,但是不能加排它锁,进行数据修改
SELECT ... LOCK IN SHARE MODE:这条语句在查询的同时给符合条件的行添加共享锁(读锁)。对选定的行进行了加锁,其他会话可以读取这些行,但是除非持有共享模式锁,否则不能对它们进行写操作(更新、删除、修改)。这个锁模式的目的主要是为了保护某个查询下的数据不被其他事务更改。 |
意向锁
在MySQL的InnoDB存储引擎中,意向锁(Intention Locks)是为了在表级上解决多粒度锁定问题(也就是同一时间在已经存在行锁或表锁的情况下允许其他事务在表级别上添加锁)而引入的一种锁。
意向锁是InnoDB自动加的,用户不需要直接操作。它们见于InnoDB存储引擎的内部,并且是在行锁之前申请的。它们有两种类型:意向共享锁(Intention Shared Lock)和意向排他锁(Intention Exclusive Lock)。这两种意向锁都是表级别的锁。
1. 意向共享锁(IS):表明事务接下来将在该表的行上请求共享锁(读锁)。存有意向共享锁的表上可以执行查询操作。
2. 意向排他锁(IX):表明事务接下来将在该表的行上请求排他锁(写锁)。存有意向排他锁的表上可以进行插入、更新、删除操作。
意向锁的设计可以让数据库在请求行级锁之前通过检查意向锁来了解是否有冲突,这样就可以大大降低锁冲突的可能性,提高多事务处理时的效率。再者,意向锁的存在也可以避免出现死锁的情况。
乐观锁
乐观锁(Optimistic Locking)并不是通过真正的锁来实现,而是在数据读取的时候,并不加任何锁;而在更新数据的时候,才会判断这段时间内别的事务是否也对这个数据进行了更新。如果别的事务有更新,那么就回滚当前事务,否则就提交事务。所以,乐观锁适用于读多写少的应用场景,能够最大程度地提高并发性能。
使用乐观锁的方式,可以避免读写、写读互斥的情况,具体如下:
1. 乐观锁实现读写不互斥:当一个事务在读取数据的时候,由于不加锁,所以并不会阻止其他事务进行写操作。此时尽管写操作可能会引起数据的变化,但是由于一致性读的保障,当前事务还是能读取到开始读取操作时的数据快照。读操作结束后,如果进行写操作,则需要判断在此期间是否有其他写操作影响了这个数据。如果有,则回滚事务,否则提交事务。
2. 乐观锁实现写读不互斥:当一个事务在写数据的时候,由于不会锁定数据,所以并不会阻止其他事务进行读操作。其他事务可以继续读取数据的旧版本。然后当前事务需要在写入时判断在此期间是否有其他写操作影响了这个数据。如果有,则回滚事务;否则,提交事务。
所以,通过乐观锁,我们可以在一定程度上解决读写和写读的阻塞问题。但是这也意味着在高并发的情况下,乐观锁就可能会导致大量的事务回滚,从而影响性能。
互斥原理
- 读读不互斥:这个是因为读操作如果使用的是共享锁(Shared Lock),在持有共享锁的情况下,是允许其他事务继续对这条数据加共享锁,进行读取操作的。也就是说,同时对同一条数据进行读操作并不会互相影响。
- 写写互斥:如果一个事务在进行写操作(如UPDATE,DELETE)时,会对这条数据加上排它锁(Exclusive Lock)。在持有排它锁的情况下,其他任何事务都无法获取这个数据行的任何锁,也就实现了写写互斥。
- 读写互斥:如果一个事务对某条数据加上了共享锁,进行读取操作,那么其他事务不能对此数据进行写操作(即不能加排它锁),实现了读写互斥。
- 写读互斥:同理,如果一个事务正在对某条数据进行写操作(也即持有排它锁),那么其他事务在此锁释放前,无法对该数据进行读操作(无法加共享锁),实现了写读互斥。
1、对MyISAM表的读操作(加读锁) ,不会阻塞其他进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其它进程的写操作。 2、对MylSAM表的写操作(加写锁) ,会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其它进程的读写操作 |
表锁/行锁
1. 表锁(Table Locks):表级锁是MySQL中最基本的锁类型,也是开销最小的锁类型。表级锁有两种:读锁(共享锁)和写锁(排他锁)。读锁在同一时间允许多个事务读取同一资源,不对数据进行修改,而写锁在任一时刻只允许一个事务进行写入或修改操作,并在此期间,不允许其他事务对数据进行操作,确保数据的正确性。
2. 行锁(Row Locks):行级锁是MySQL中最细粒度的锁,它针对数据表中的行进行加锁。行级锁可以最大程度地支持并发处理(同时也带来了最高的开销)。行级锁只在存储引擎层实现,MyISAM和MEMORY存储引擎的行锁只是一个功能声明而未实现,但InnoDB存储引擎实现了行级锁。行级锁有两种模式:共享(S)锁和排他(X)锁。
这两种锁类型各有其使用场景,表锁适合查询密集型的场景,而行锁则更适合大量更新、插入操作的应用场景。
间隙锁和临键锁
在MySQL中,间隙锁(Gap Locks)和临键锁(Next-Key Locks)都是InnoDB存储引擎提供的行级锁的一种扩展,主要用于解决隔离级别为可重复读和串行化时的幻读问题。
间隙锁(Gap Locks):顾名思义,间隙锁是锁定一个范围,但并不包含这个范围内的任何具体的行,而只是防止新行插入到这个范围中。实现方式是锁定索引的记录之间的间隙。由于间隙锁和行级锁可以共存,因此InnoDB可以有效地避免幻读(Phantom Read)现象。
临键锁(Next-Key Locks):临键锁是InnoDB在行级锁和间隙锁基础上的一种组合,是InnoDB默认的行级锁。一个临键锁实际上锁定了索引记录及其之间的间隙。因此,临键锁可以同时防止同一资源的并发修改(即解决脏写问题)和并发插入的记录导致的幻读(即解决幻读问题)。
这两种锁主要是在InnoDB存储引擎的可重复读(REPEATABLE READ)和串行化(SERIALIZABLE)两个事务隔离级别中使用。这两种锁的存在,使得InnoDB可以实现精确度高和效率高的锁定策略。
死锁
大多数情况mysql可以自动检测死锁并回滚产生死锁的那个事务,但是有些情况mysql没法自动检测死锁
锁优化建议
● 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁
● 合理设计索引,尽量缩小锁的范围
● 尽可能减少检索条件范围,避免间隙锁
● 尽量控制事务大小,减少锁定资源量和时间长度,涉及事务加锁的sql尽量放在事务最后执行
● 尽可能低级别事务隔离
个人公众号
对于刚毕业的应届生或者忙于找工作的求职者, 可以一起沟通哦 |