数据库的事务(Transaction)是一种机制、一个操作序列,包含了一组数据库操作命令。事务把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么都执行,要么都不执行,因此事务是一个不可分割的工作逻辑单元。
在数据库系统上执行并发操作时,事务是作为最小的控制单元来使用的,特别适用于多用户同时操作的数据库系统。例如,订票系统、银行、电商以及证券交易系统等。
原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),这 4 个特性通常简称为 ACID。
当多个事务并发执行时,可能出现以下问题:
不可重复度和幻读看起来比较像,它们主要的区别是:在不可重复读中,发现数据不一致主要是数据被 更新了。在幻读中,发现数据不一致主要是数据增多或者减少了。
事务具有隔离性,理论上来说事务之间的执行不应该相互产生影响,其对数据库的影响应该和它们串行执行时一样。然而完全的隔离性会导致系统并发性能很低,降低对资源的利用率,因而实际上对隔离性的要求会有所放宽,这也会一定程度造成对数据库一致性要求降低。
SQL 标准为事务定义了不同的隔离级别,从低到高依次是:
隔离级别 | 脏读 | 不可重复读 | 丢失更新 | 幻读 |
---|---|---|---|---|
Read Uncommited | Yes | Yes | Yes | Yes |
Read Committed | No | Yes | Yes | Yes |
Repeatable Read | No | No | No | Yes |
Serializable | No | No | No | No |
在 MySQL 中,为了提高数据库的性能,MySQL 采用了 WAL(Write-Ahead Logging)机制,即客户端在修改数据的过程后,并不会立马对硬盘中的数据进行更新。
这样做的原因在于,如果每次客户端进行数据更改后,立马对磁盘中的数据进行更改的话,那么磁盘的压力是非常大的。
WAL 机制,主要的操作是先写日志,先在一个日志中记录了 MySQL 要对硬盘中的存储 MySQL 数据的数据页中的数据进行什么样的更改,等到 Mysql 空闲的时候再进行同步操作到硬盘中。
可能有人会很疑惑,同样是操作磁盘,为什么写日志比直接改数据会更加高效?
答案在于写日志是顺序写,直接改磁盘是随机写。所以同样是写,它们写的速度有着天壤之别。这也是 WAL 的另外一个好处。
数据库只做两件事情:存储数据、检索数据。而索引是在你存储的数据之外,额外保存一些路标(一般是 B+树),以减少检索数据的时间。所以索引是主数据衍生的附加结构。
索引是对数据表的一列活多列的值进行排序的一种结构,使用索引考研快速访问数据表中的特定信息。
优点:
缺点:
表记录的排列顺序和索引的排列顺序一致,所以查询效率快,只要找到第一个索引值记录,其余连续性的记录在物理上一样连续存放。聚集索引的缺点就是修改慢,因为为了使表记录和索引的排列顺序一致,在插入记录的时候,会对数据页重新排序。
表记录和索引的排列顺序不一定一致,两种索引都采用 B+树的结构,非聚集索引的叶子层并不和实际数据页相重叠,而采用叶子层包含一个指向表记录的指针。非聚集索引层次多,不会造成数据重排。
最基本的索引,它没有任何的限制。
索引列的值必须唯一,允许有空值。
它是一种特殊的唯一索引,不允许有空值。
又叫做多列索引,联合索引。多个字段上建立的索引,提高复合条件查询的速度
对文本的内容进行分词,进行搜索。
索引的数据结构主要有 B+树和哈希表,对应的索引分别为 B+树索引和哈希索引。InnoDB 引擎的索引类 型有 B+树索引和哈希索引,默认的索引类型为 B+树索引。
一个 B+树有以下特征:
那么我们先来看一个 B+树的图:
所有的数据都在叶子节点,且每一个叶子节点都带有指向下一个节点的指针,形成了一个有序的链表。
为什么要有序呢?其实是为了范围查询。比如说 select * from Table where id > 1 and id < 100; 当找到 1 后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,极大提到了区间查询效率。
以下为 B+树的优势:
事务之间的隔离,是通过锁机制实现的。当一个事务需要对数据库中的某行数据进行修改时,需要先给数据加锁;加了锁的数据,其它事务是不运行操作的,只能等待当前事务提交或回滚将锁释放。
按照粒度划分:行锁、表锁、页锁
按照使用方式划分:共享锁、排它锁
按照思想划分:悲观锁、乐观锁
粒度:指数据仓库的数据单位中保存数据的细化或综合程度的级别。细化程度越高,粒度级就越小;相反,细化程度越低,粒度级就越大。
行锁:粒度最小的锁,表示只针对当前操作的行进行加锁;
表锁:粒度最大的锁,表示当前的操作对整张表加锁;
页锁:粒度介于行级锁和表级锁中间的一种锁,表示对页进行加锁。
排它锁(exclusive lock):排他锁又叫写锁,如果事务 T 对 A 加上排它锁,则其他事务都不能对 A 加任何类型的锁。获准排它锁的事务既能读数据,又能写数据
共享锁(share lock):共享锁又叫读锁,如果事务 T 对 A 加上共享锁,则其他事务只能对 A 再加共享锁,不能加其他锁。共享锁的事务只能读数据,不能写数据。
悲观锁:是基于一种悲观的态度类来防止一切数据冲突,它是以一种预防的姿态在修改数据之前把数据锁住,然后再对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作,直到前面一个人把锁释放后下一个人数据加锁才可对数据进行加锁,然后才可以对数据进行操作,一般数据库本身锁的机制都是基于悲观锁的机制实现的。
乐观锁:是对于数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁(这使得多个任务可以并行的对数据进行操作),只有到数据提交的时候才通过一种机制来验证数据是否存在冲突(一般实现方式是通过加版本号然后进行版本号的对比方式实现)。
数据库存储引擎是数据库底层软件组件,数据库管理系统使用数据引擎进行创建、查询、更新和删除数据操作。不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能,使用不同的存储引擎还可以获得特定的功能。
MySQL5.5 之前的默认存储引擎是 MyISAM,5.5 之后就改为了 InnoDB。
特点 | InnoDB | MyISAM |
---|---|---|
事务安全 | 支持 | 不支持 |
锁机制 | 表锁和行锁(适合高并发) | 表锁 |
可恢复性 | 事务日志进行恢复 | 无事务日志 |
内存使用 | 高 | 低 |
批量插入速度 | 低 | 高 |
支持外键 | 支持 | 不支持 |
使得数据可以从一个数据库服务器复制到其他服务器上,在复制数据时,一个服务器充当主服务器(master),其余的服务器充当从服务器(slave)。
一句话表示就是,主数据库做什么,从数据库就跟着做什么。
整体上来说,复制有 3 个步骤:
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 会自动从半同步复制切换为异步复制,当从库恢复正常连接到主库后,主库又会自动切换回半同步复制。
只有主库提供读写服务,备库仅留作备用。
两个主库同时提供服务,负载均衡。
一主多从,读写分离。
读写分离就是只在主数据库服务器上写,只在从数据库服务器上读。基本的原理是让主数据库服务器处理事务性查询,而从数据库服务器处理 select 查询。数据库复制被用来把主数据库服务器上数据变更同步到集群中的从数据库服务器。
事务性查询:就是把查询放到一个事务中,而带有的事务操作则会落到主数据库服务器。
因为数据库的"写”(写 10000 条数据可能要 3 分钟)操作是比较耗时的。但是数据库的"读”却很快(读 10000 条数据可能只要 5 秒钟)。所以读写分离解决的是,数据库的写入影响了查询的效率问题。
第一种:磁盘读 IO 瓶颈,热点数据太多,数据库缓存放不下,每次查询时会产生大量的 IO,降低查询速度 -> 分库和垂直分表。
第二种:网络 IO 瓶颈,请求的数据太多,网络带宽不够 -> 分库。
第一种:SQL 问题,如 SQL 中包含 join、group by、order by、非索引字段条件查询等,增加 CPU 运算的操作 -> SQL 优化,建立合适的索引,在业务 Service 层进行业务计算。
第二种:单表数据量太大,查询时扫描的行太多,SQL 效率低,CPU 率先出现瓶颈 -> 水平分表。
分库方式:以字段为依据,按照一定策略(hash、range 等),将一个库中的数据拆分到多个库中。
分库结果:
分表概念:以字段为依据,按照一定策略(hash、range 等),将一个表中的数据拆分到多个表中。
分表结果:
分库方式:以表为依据,按照业务归属不同,将不同的表拆分到不同的库中。
分库结果:
分表方式:以字段为依据,按照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中。
分表结果: