1. 事务
1.1 什么是数据库事务?
数据库的事务(Transaction)是一种机制、一个操作序列,包含了一组数据库操作命令。事务把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么都执行,要么都不执行,因此事务是一个不可分割的工作逻辑单元。
在数据库系统上执行并发操作时,事务是作为最小的控制单元来使用的,特别适用于多用户同时操作的数据库系统。例如,订票系统、银行、电商以及证券交易系统等。
1.2 事务的四大特性
原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),这 4 个特性通常简称为 ACID。
- 原子性:事务是一个完整的操作。事务的各元素是不可分的(原子的)。事务中的所有元素必须作为一个整体提交或回滚。如果事务中的任何元素失败,则整个事务将失败。
- 一致性:事务的执行结果必须使数据库从一个一致性状态到另一个一致性状态。
- 隔离性:对数据进行修改的所有并发事务是彼此隔离的, 这表明事务必须是独立的,它不应以任何方式依赖于或影响其他事务。
- 持久性:事务一旦提交,其对数据库的更新就是持久的。任何事务或系统故障都不会导致数据丢失。
2. 事务隔离级别
2.1 数据库并发问题
当多个事务并发执行时,可能出现以下问题:
- 脏读:事务 A 更新了数据,但还没有提交,这时事务 B 读取到事务 A 更新后的数据,然后事务 A 回滚了,事务 B 读取到的数据就成为脏数据了。
- 不可重复读:事务 A 对数据进行多次读取,事务 B 在事务 A 多次读取的过程中执行了更新操作并提交了,导致事务 A 多次读取到的数据并不一致。
- 幻读:事务 A 在读取数据后,事务 B 向事务 A 读取的数据中插入了几条数据,事务 A 再次读取数据时发现多了几条数据,和之前读取的数据不一致。
- 丢失修改:事务 A 和事务 B 都对同一个数据进行修改,事务 A 先修改,事务 B 随后修改,事务 B 的修改 覆盖了事务 A 的修改。
不可重复度和幻读看起来比较像,它们主要的区别是:在不可重复读中,发现数据不一致主要是数据被 更新了。在幻读中,发现数据不一致主要是数据增多或者减少了。
2.2 事务隔离级别
事务具有隔离性,理论上来说事务之间的执行不应该相互产生影响,其对数据库的影响应该和它们串行执行时一样。然而完全的隔离性会导致系统并发性能很低,降低对资源的利用率,因而实际上对隔离性的要求会有所放宽,这也会一定程度造成对数据库一致性要求降低。
SQL 标准为事务定义了不同的隔离级别,从低到高依次是:
- 读未提交 (READ UNCOMMITTED):所有事务都可以看到其它未提交事务的执行结果,该隔离级别一般不会使用。
- 读已提交 (READ COMMITTED):一个事务只能看到已经提交的事务所做的变更。
- 可重复读 (REPEATABLE READ):确保同一事务的多个实例在并发读取数据时会看到相同的数据行。
- 串行化 (SERIALIZABLE):完全串行化读,每次读都需要获得表级共享锁,读写相互阻塞。
隔离级别 | 脏读 | 不可重复读 | 丢失更新 | 幻读 |
---|---|---|---|---|
Read Uncommited | Yes | Yes | Yes | Yes |
Read Committed | No | Yes | Yes | Yes |
Repeatable Read | No | No | No | Yes |
Serializable | No | No | No | No |
3. 日志
3.1WAL 机制
在 MySQL 中,为了提高数据库的性能,MySQL 采用了 WAL(Write-Ahead Logging)机制,即客户端在修改数据的过程后,并不会立马对硬盘中的数据进行更新。
这样做的原因在于,如果每次客户端进行数据更改后,立马对磁盘中的数据进行更改的话,那么磁盘的压力是非常大的。
WAL 机制,主要的操作是先写日志,先在一个日志中记录了 MySQL 要对硬盘中的存储 MySQL 数据的数据页中的数据进行什么样的更改,等到 Mysql 空闲的时候再进行同步操作到硬盘中。
可能有人会很疑惑,同样是操作磁盘,为什么写日志比直接改数据会更加高效?
答案在于写日志是顺序写,直接改磁盘是随机写。所以同样是写,它们写的速度有着天壤之别。这也是 WAL 的另外一个好处。
3.2 MySQL 常见日志分类
- 错误日志(errorlog):错误日志记录着 mysqld 启动和停止,以及服务器在运行过程中发生的错误及警告相关信息。当数据库意外宕机或发生其他错误时,我们应该去排查错误日志。
- 慢查询日志(slow query log):慢查询日志是用来记录执行时间超过 long_query_time 这个变量定义的时长的查询语句。通过慢查询日志,可以查找出哪些查询语句的执行效率很低,以便进行优化。
- 一般查询日志(general log):一般查询日志又称通用查询日志,是 MySQL 中记录最详细的日志,该日志会记录 mysqld 所有相关操作,当 clients 连接或断开连接时,服务器将信息写入此日志,并记录从 clients 收到的每个 SQL 语句。当你怀疑 client 中的错误并想要确切知道 client 发送给 mysqld 的内容时,通用查询日志非常有用。
- 二进制日志(binlog):它记录了数据库所有执行的 DDL 和 DML 语句,以事件形式记录并保存在二进制文件中。常用于数据恢复和主从复制。
- 中继日志(relay log):中继日志用于主从复制架构中的从服务器上,从服务器的 slave 进程从主服务器处获取二进制日志的内容并写入中继日志,然后由 IO 进程读取并执行中继日志中的语句。
- 重做日志(redo log):确保事务的持久性。redo 日志记录事务执行后的状态,用来恢复未写入 data file 的已成功事务更新的数据。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启 mysql 服务的时候,根据 redo log 进行重做,从而达到事务的持久性这一特性。
- 回滚日志(undo log):保证数据的原子性,当事务对数据库进行修改,InnoDB 引擎不仅会记录 redo log,还会生成对应的 undo log 日志;如果事务执行失败或调用了 rollback,导致事务需要回滚,就可以利用 undo log 中的信息将数据回滚到修改之前的样子。
4. 索引
4.1 什么是索引
数据库只做两件事情:存储数据、检索数据。而索引是在你存储的数据之外,额外保存一些路标(一般是 B+树),以减少检索数据的时间。所以索引是主数据衍生的附加结构。
索引是对数据表的一列活多列的值进行排序的一种结构,使用索引考研快速访问数据表中的特定信息。
4.2. 索引的优缺点
优点:
- 索引能够提高数据检索的效率,降低数据库的 IO 成本。
- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性,创建唯一索引
- 在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间
- 加速两个表之间的连接,一般是在外键上创建索引
缺点:
- 需要占用物理空间,建立的索引越多需要的空间越大
- 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加
4.3. 索引分类
4.3.1 按物理存储角度分
聚集索引
表记录的排列顺序和索引的排列顺序一致,所以查询效率快,只要找到第一个索引值记录,其余连续性的记录在物理上一样连续存放。聚集索引的缺点就是修改慢,因为为了使表记录和索引的排列顺序一致,在插入记录的时候,会对数据页重新排序。
非聚集索引
表记录和索引的排列顺序不一定一致,两种索引都采用 B+树的结构,非聚集索引的叶子层并不和实际数据页相重叠,而采用叶子层包含一个指向表记录的指针。非聚集索引层次多,不会造成数据重排。
4.3.2 按逻辑角度分
普通索引
最基本的索引,它没有任何的限制。
唯一索引
索引列的值必须唯一,允许有空值。
主键索引
它是一种特殊的唯一索引,不允许有空值。
复合索引
又叫做多列索引,联合索引。多个字段上建立的索引,提高复合条件查询的速度
全文索引
对文本的内容进行分词,进行搜索。
4.4 索引的使用场景
- 对于中大型表建立索引非常有效,对于非常小的表,一般全部表扫描速度更快些。
- 对于超大型的表,建立和维护索引的代价也会变高,这时可以考虑分区技术。
- 如何表的增删改非常多,而查询需求非常少的话,那就没有必要建立索引了,因为维护索引也是需 要代价的。
- 一般不会出现再 where 条件中的字段就没有必要建立索引了。
- 多个字段经常被查询的话可以考虑联合索引。
- 字段多且字段值没有重复的时候考虑唯一索引。
- 字段多且有重复的时候考虑普通索引。
5. 索引底层实现
索引的数据结构主要有 B+树和哈希表,对应的索引分别为 B+树索引和哈希索引。InnoDB 引擎的索引类 型有 B+树索引和哈希索引,默认的索引类型为 B+树索引。
一个 B+树有以下特征:
- 有 n 个子树的中间节点包含 n 个元素,每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。
- 所有叶子节点包含元素的信息以及指向记录的指针,且叶子节点按关键字自小到大顺序链接。
- 所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。
那么我们先来看一个 B+树的图:
所有的数据都在叶子节点,且每一个叶子节点都带有指向下一个节点的指针,形成了一个有序的链表。
为什么要有序呢?其实是为了范围查询。比如说 select * from Table where id > 1 and id < 100; 当找到 1 后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,极大提到了区间查询效率。
以下为 B+树的优势:
- 单一节点存储更多元素,减少 IO
- 所有查询都要找到叶子节点,查询稳定
- 所有叶子节点形成有序链表,方便范围查询
- 一般性情况,数据库的 B+树的高度一般在 2~4 层,这就是说找到某一键值的行记录最多需要 2 到 4 次逻辑 IO,相当于 0.02 到 0.04s。
6. 数据库锁
6.1 锁机制
事务之间的隔离,是通过锁机制实现的。当一个事务需要对数据库中的某行数据进行修改时,需要先给数据加锁;加了锁的数据,其它事务是不运行操作的,只能等待当前事务提交或回滚将锁释放。
6.2 锁的分类
按照粒度划分:行锁、表锁、页锁
按照使用方式划分:共享锁、排它锁
按照思想划分:悲观锁、乐观锁
6.3 粒度
粒度:指数据仓库的数据单位中保存数据的细化或综合程度的级别。细化程度越高,粒度级就越小;相反,细化程度越低,粒度级就越大。
行锁:粒度最小的锁,表示只针对当前操作的行进行加锁;
表锁:粒度最大的锁,表示当前的操作对整张表加锁;
页锁:粒度介于行级锁和表级锁中间的一种锁,表示对页进行加锁。
6.4 使用
排它锁(exclusive lock):排他锁又叫写锁,如果事务 T 对 A 加上排它锁,则其他事务都不能对 A 加任何类型的锁。获准排它锁的事务既能读数据,又能写数据
共享锁(share lock):共享锁又叫读锁,如果事务 T 对 A 加上共享锁,则其他事务只能对 A 再加共享锁,不能加其他锁。共享锁的事务只能读数据,不能写数据。
6.5 思想
悲观锁:是基于一种悲观的态度类来防止一切数据冲突,它是以一种预防的姿态在修改数据之前把数据锁住,然后再对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作,直到前面一个人把锁释放后下一个人数据加锁才可对数据进行加锁,然后才可以对数据进行操作,一般数据库本身锁的机制都是基于悲观锁的机制实现的。
乐观锁:是对于数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁(这使得多个任务可以并行的对数据进行操作),只有到数据提交的时候才通过一种机制来验证数据是否存在冲突(一般实现方式是通过加版本号然后进行版本号的对比方式实现)。
7. MySQL 存储引擎
7.1 什么是存储引擎
数据库存储引擎是数据库底层软件组件,数据库管理系统使用数据引擎进行创建、查询、更新和删除数据操作。不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能,使用不同的存储引擎还可以获得特定的功能。
7.2 MySQL 常用存储引擎有哪些?
- InnoDB 事务型数据库的首选引擎,支持事务安全表(ACID),支持行锁定和外键。
- MyISAM 存储引擎,并发性比较差,不支持事物和外键等操作,默认的锁粒度为表级锁。
MySQL5.5 之前的默认存储引擎是 MyISAM,5.5 之后就改为了 InnoDB。
特点 | InnoDB | MyISAM |
---|---|---|
事务安全 | 支持 | 不支持 |
锁机制 | 表锁和行锁(适合高并发) | 表锁 |
可恢复性 | 事务日志进行恢复 | 无事务日志 |
内存使用 | 高 | 低 |
批量插入速度 | 低 | 高 |
支持外键 | 支持 | 不支持 |
8. MySQL 主从同步
8.1 什么是主从同步
使得数据可以从一个数据库服务器复制到其他服务器上,在复制数据时,一个服务器充当主服务器(master),其余的服务器充当从服务器(slave)。
一句话表示就是,主数据库做什么,从数据库就跟着做什么。
8.2 主从同步的好处
- 为实现服务器负载均衡/读写分离做铺垫,提升访问速度。
- 通过复制实现数据的异地备份,保障数据安全。
- 提高数据库系统的可用性,主库宕机后,从库尚可用,即当主服务器出现问题时,数据库管理员可以马上让从服务器作为主服务器,用来数据的更新与查询服务。
8.3 主从同步原理
整体上来说,复制有 3 个步骤:
- master 将改变记录到二进制日志 (binary log) 中。
- slave 的 io 线程将 master 的 binary log events 拷贝到它的中继日志 (relay log)。
- slave 的 sql 线程解析中继日志中的事件并在从库执行,保持与主库一致。
8.4 主从复制的方式
异步复制
MySQL 的复制默认是异步的,主从复制至少需要两个 MYSQL 服务,这些 MySQL 服务可以分布在不同的服务器上,也可以在同一台服务器上。
MySQL 主从异步复制是最常见的复制场景。数据的完整性依赖于主库 BINLOG 的不丢失,只要主库的 BINLOG 不丢失,那么就算主库宕机了,我们还可以通过 BINLOG 把丢失的部分数据通过手工同步到从库上去。
多线程复制
在 MySQL5.7 中,带来了全新的多线程复制技术,解决了当 master 同一个 schema 下的数据发生了变更,从库不能并发应用的问题,同时也真正将 binlog 组提交的优势充分发挥出来,保障了从库并发应用 Relay Log 的能力。
增强半同步复制
前面介绍的复制是异步操作,主库和从库的数据之间难免会存在一定的延迟,这样存在一个隐患:当在主库上写入一个事务并提交成功,而从库尚未得到主库的 BINLOG 日志时,主库由于磁盘损坏、内存故障、断电等原因意外宕机,导致主库上该事务 BINLOG 丢失,此时从库就会损失这个事务,从而造成主从不一致。
从 MySQL5.5 开始,支持半同步复制(Semi synchronous Replication)在一定程度上保证提交的事务已经传给了至少一个备库。
1、一个事务操作的完成需要记完两份日志,即主从的 binlog 是同步的 半同步复制,当主库每提交一个事务后,不会立即返回,而是等待其中一个从库接收到 Binlog 并成功写入 Relay-log 中才返回客户端,所以这样就保证了一个事务至少有两份日志,一份保存在主库的 Binlog,另一份保存在其中一个从库的 Relay-log 中,从而保证了数据的安全性和一致性。
2、半同步即并非完全同步 半同步复制的“半”体现在,虽然主从库的 Binlog 是同步的,但主库不会等待从库的 sql 线程执行完 Relay-log 后才返回,而是确认从库的 io 线程接收到 Binlog,达到主从 Binlog 同步的目的后就返回了,所以从库的数据对于主库来说还是有延时的,这个延时就是从库 sql 线程执行 Relay-log 的时间。所以只能称为半同步。
3、半同步复制超时则会切换回异步复制,正常后则切回半同步复制 在半同步复制时,如果主库的一个事务提交成功了,在推送到从库的过程当中,从库宕机了或网络故障,导致从库并没有接收到这个事务的 Binlog,此时主库会等待一段时间(这个时间由 rpl_semi_sync_master_timeout 的毫秒数决定),如果这个时间过后还无法推送到从库,那 MySQL 会自动从半同步复制切换为异步复制,当从库恢复正常连接到主库后,主库又会自动切换回半同步复制。
8.5 主从常用架构
主备架构
只有主库提供读写服务,备库仅留作备用。
双主架构
两个主库同时提供服务,负载均衡。
主从架构
一主多从,读写分离。
双主+主从架构
9. MySQL 读写分离
9.1 什么是读写分离
读写分离就是只在主数据库服务器上写,只在从数据库服务器上读。基本的原理是让主数据库服务器处理事务性查询,而从数据库服务器处理 select 查询。数据库复制被用来把主数据库服务器上数据变更同步到集群中的从数据库服务器。
事务性查询:就是把查询放到一个事务中,而带有的事务操作则会落到主数据库服务器。
9.2 为什么要做读写分离
因为数据库的"写”(写 10000 条数据可能要 3 分钟)操作是比较耗时的。但是数据库的"读”却很快(读 10000 条数据可能只要 5 秒钟)。所以读写分离解决的是,数据库的写入影响了查询的效率问题。
10. MySQL 分库分表
10.1 数据库瓶颈
10.1.1 IO 瓶颈
第一种:磁盘读 IO 瓶颈,热点数据太多,数据库缓存放不下,每次查询时会产生大量的 IO,降低查询速度 -> 分库和垂直分表。
第二种:网络 IO 瓶颈,请求的数据太多,网络带宽不够 -> 分库。
10.1.2 CPU 瓶颈
第一种:SQL 问题,如 SQL 中包含 join、group by、order by、非索引字段条件查询等,增加 CPU 运算的操作 -> SQL 优化,建立合适的索引,在业务 Service 层进行业务计算。
第二种:单表数据量太大,查询时扫描的行太多,SQL 效率低,CPU 率先出现瓶颈 -> 水平分表。
10.2 分库分表
10.2.1 水平分库
分库方式:以字段为依据,按照一定策略(hash、range 等),将一个库中的数据拆分到多个库中。
分库结果:
- 每个库的结构都一样;
- 每个库的数据都不一样,没有交集;
- 所有库的并集是全量数据;
10.2.2 水平分表
分表概念:以字段为依据,按照一定策略(hash、range 等),将一个表中的数据拆分到多个表中。
分表结果:
- 每个表的结构都一样;
- 每个表的数据都不一样,没有交集;
- 所有表的并集是全量数据;
10.2.3 垂直分库
分库方式:以表为依据,按照业务归属不同,将不同的表拆分到不同的库中。
分库结果:
- 每个库的结构都不一样;
- 每个库的数据也不一样,没有交集;
- 所有库的并集是全量数据;
10.2.4 垂直分表
分表方式:以字段为依据,按照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中。
分表结果:
- 每个表的结构都不一样;
- 每个表的数据也不一样,一般来说,每个表的字段至少有一列交集,一般是主键,用于关联数据;
- 所有表的并集是全量数据;