MYSQL的逻辑架构
第一层,客户端层:包含了连接处理,身份验证,确保安全性等
第二层,包含了mysql大部分的核心功能,查询解析,分析,优化,以及所有的内置函数(例如,日期,时间函数),还有存储过程,触发器,视图等都是在这一次实现的
第三层,存储引擎层,负责MYSQL中数据的存储和提取
连接管理与安全性
默认情况下,每个客户端连接都会在服务器进程中拥有一个单独的线程。
服务器维护一个缓存区,用于存放已就绪的线程,因此不需要为每个新的连接创建或者销毁线程,就是线程池思想。
客户端请求连接到Mysql数据库时,服务器需要对客户端身份进行验证,身份验证基于用户名,发起的主机名和密码。
客户端连接成功后,服务器会继续验证该客户端是否有其发出的查询的权限,(例如,是否允许客户端对world数据库中的Students表执行SELECT语句)
优化与执行
对于sql语句,mysql会解析然后创建解析树,再对其进行各种优化,包括(重写查询,决定表的读取顺序,选择合适的索引)
存储引擎对于查询优化是有影响的,优化器会向存储引擎询问它的一些功能,某个具体操作的成本,以及表数据的统计信息。
并发控制
共享锁(读锁)和排他锁(写锁)
当多个客户端需要同时修改数据,会出现并发控制问题
MYSQL有2个级别的并发控制:服务器级别和存储引擎级别
处理并发读/写访问的系统通常实现一个由 两种锁类型组成的锁系统。这两种锁通常被称为共享锁(shared lock)和排他锁 (exclusive lock),也叫读锁(read lock)和写锁(write lock)。
共享锁(读锁):一个客户端对某个资源进行读取时,会给这个资源加上一个共享锁,这个锁是共享的,多个客户端可以同时读取该资源
排他锁(写锁):一个客户端对某个资源进行写操作时,会给该资源添加排他锁,此时其他客户端想读或者写该资源都会被阻塞。排他锁既会阻塞写锁,也会阻塞读锁。
锁的粒度
锁加在什么位置也是一个需要研究的问题,比如锁一整个表或者只锁一行。
对于提高资源的并发性来说,当然是锁的粒度越小越好,就是只锁定包含需要修改的部分数据。
但是,加锁也是要消耗资源的,锁的粒度越小,消耗的资源就越大。
因为获取锁,检查锁是否空闲,释放锁等,都会增加系统的开销。
MYSQL提供了多种选择来平衡锁开销与数据安全性。
每种MYSQL存储引擎都可以实现自己的锁策略和锁粒度。
两种最重要的锁策略
表锁
表锁是MYSQL种最基本也是开销最小的锁策略,当一个客户端向对表进行写操作时,需要先获得一个写锁,这会阻塞其他所有客户端对该表的所有读写操作。只有没有客户端在对表进行写操作时,其他客户端才能获得读锁
行锁
行级锁可以最大程度地支持并发处理(也带来了最大的锁开销)。
当一个客户端对某一行数据进行写操作时,只会为该行添加排他锁,其他客户端可以对其他行进行读写操作。
行级锁时在存储引擎而不是服务器中实现的。
事务
ACID
原子性 (atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。
原子性:将一组sql语句看作一个不可分割的工作单元,要么都成功提交,要么都失败回滚
一致性:数据库总是从一个一致性状态转换到下一个一致性状态
隔离性:通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的
持久性:一旦提交,事务所做的修改就会被永久保存到数据库中。
事务隔离级别
读未提交:
一个事务中会读取到其他事务中还没提交的修改
读已提交:
事务只能读取到其他事务以及提交了的修改,在这种隔离级别下,系统满足事务的基本概念。
会出现不可重复读问题。
可重复读:
在一个事务中的多次相同的查询语句得到的结果是一致的。这么做到的呢,就是读早于本事务开始的且已经提交的事务对数据所作的修改
会出现幻读问题,就是在第二次运行相同查询时发现结果行数与第一次查询得到的行数不一致。
可串行化:
使得事务按照严格的顺序执行,也就不存在并发问题了,代价是性能下降。
死锁
2个事务互相等待对方释放某个资源的锁,或者说产生了循环依赖。
例如
事务A
事务B
每个事务都开始执行第一个查询,在处理过程中会更新一行数据,同时在主键索引和其他
唯一索引中将该行锁定。然后,每个事务将在第二个查询中尝试更新第二行数据,却发现
该行已经被锁定。这两个事务将永远等待对方完成。
解除死锁的办法:就是让其中一个事务回滚,这样就把资源重新释放了出来。
InnoDB目前处理死锁的方式是将持有最少行级排他锁的事务回滚。
事务日志
事务日志有助于提高事务的效率。存储引擎只需要更改内存中的数据副本,而不用每次修
改磁盘中的表,这会非常快。然后再把更改的记录写入事务日志中,事务日志会被持久化
保存在硬盘上。因为事务日志采用的是追加写操作,是在硬盘中一小块区域内的顺序
I/O,而不是需要写多个地方的随机I/O,所以写入事务日志是一种相对较快的操作。最后
会有一个后台进程在某个时间去更新硬盘中的表。因此,大多数使用这种技术(write
ahead logging,预写式日志)的存储引擎修改数据最终需要写入磁盘两次。
如果修改操作已经写入事务日志,那么即使系统在数据本身写入硬盘之前发生崩溃,存储
引擎仍可在重新启动时恢复更改。具体的恢复方法则因存储引擎而异。
mysql中的事务
要讲mysql中的事务,实际上就是讲最推荐使用的引擎InnoDB存储引擎的事务了
AUTOCOMMIT 自动提交
默认情况下,单个INSERT、UPDATE或DELETE语句会被隐式包装在一个事务中并在执
行成功后立即提交,这称为自动提交(AUTOCOMMIT)模式。
可以使用SET命令设置AUTOCOMMIT变量来启用或禁用自动提交模式。
启用可以设置为1或者ON,禁用可以设置为0或者OFF。
如果关闭该模式,就只有等到发出COMMIT或者ROLLBACK才结束当前事务,然后MySQL会
立即启动一个新的事务
当启用AUTOCOMMIT时,也可以使用关键字BEGIN或者 START TRANSACTION来开始一个多语句的事务
设置隔离级别
MySQL可以通过执行SET TRANSACTION ISOLATION LEVEL命令来设置隔离级别。新
的隔离级别会在下一个事务开始的时候生效。可以在配置文件中设置整个服务器的隔离级
别,也可以只改变当前会话的隔离级别:
建议最好在服务器级别设置最常用的隔离,并且只在显式情况下修改。MySQL可以识别
所有4个ANSI标准的隔离级别,InnoDB也支持这些隔离级别。
事务中不要混合使用存储引擎
读取视图(Read View)是数据库系统中用于实现多版本并发控制(MVCC)的一个关键概念。它用于确定在事务启动时或者事务执行期间,哪些数据版本对于该事务是可见的。
mvcc(多版本并发控制)
当前读
像 select lock in share mode (共享锁), select for update; update; insert; delete (排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁
快照读
像不加锁的 select 操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即 MVCC ,可以认为 MVCC 是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本
MVCC 带来的好处是?
多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。
MVCC的实现原理
MVCC 的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的 3个隐式字段,undo日志 , Read View 来实现的。
3个隐式字段
DB_TRX_ID 最近修改(修改/插入)事务 ID
6 byte,记录创建这条记录/最后一次修改该记录的事务 ID
DB_ROLL_PTR 指向这条记录的上一个版本
7 byte,回滚指针,指向这条记录的上一个版本(存储于 rollback segment 里)
DB_ROW_ID 隐藏主键
6 byte,隐含的自增 ID(隐藏主键),如果数据表没有主键,InnoDB 会自动以DB_ROW_ID产生一个聚簇索引
undo日志
实际上在执行更新操作时,是这样的:
1.先把要修改的行加上排他锁
2.把要修改的行进行备份
3.进行对应的修改操作
4.释放锁
Read View 读视图
Read View是在进行快照读(即不加锁的select)时生成的,可以简单理解为含有3个用来判断应该读取该行数据的哪个版本的指标,根据这三个指标来判断应该使用哪个版本。
1.一个含有此时活跃的事务id列表
2.活跃事务id列表中最小的那个id
3.一个当前出现过的最大事务+1
假设此时我要查一条数据,
先生成读视图,然后拿要的这条数据的隐式字段中的DB_TRX_ID ( 最近修改(修改/插入)事务 ID )和读视图的三个指标进行比较:
1.首先拿这个最近事务id与读视图中的2来比较,
如果是小于,就是说在读视图生成前,该事务已经提交,所以查到的就是这个数据;
如果是大于或者等于,就进行第二个判断
2.拿这个最近事务id与读视图中的3来比较
如果是大于或者等于,就是说在读视图生成后才开始的这个事务,那这数据自然是不可见的
如果是小于,就是在生成读视图期间开始的这个事务,那么要进行第三个判断
3.判断这个最近事务id是否包含在活跃列表里面,
如果包含,就说明在读视图生成成功前这个事务还在活跃,就是还没提交,那我当前事务就是看不见这个修改的
如果不包含,就说明在读视图生成成功时事务已经提交,那么我当前事务就是能看见这个修改后的数据的。