MySQL Server架构自顶向下大致可以分1网络连接层、2服务层、3存储引擎层和4系统文件层
1.网络连接层
提供与MySQL服务器建立的支持
2.服务层
服务层是MySQL Server的核心,主要包含1.系统管理和控制工具2.连接池3.SQL接口4.解析器5.查询优化器6.缓存六个部分
3.存储引擎层
负责MySQL中数据的存储与提取,与底层系统文件进行交互
4.系统文件层
该层负责将数据库的数据和日志存储在文件系统之上,并完成与存储引擎的交互,是文件的物理存储层。主要包含日志文件,数据文件,配置文件,pid 文件,socket 文件等
(重要)
①建立连接(Connectors&Connection Pool),通过客户端/服务器通信协议与MySQL建立连
接。MySQL 客户端与服务端的通信方式是 “ 半双工 ”。对于每一个 MySQL 的连接,时刻都有一个线程状态来标识这个连接正在做什么
②查询缓存(Cache&Buffer),这是MySQL的一个可优化查询的地方,如果开启了查询缓存且在查询缓存过程中查询到完全相同的SQL语句,则将查询结果直接返回给客户端;如果没有开启查询缓存或者没有查询到完全相同的 SQL 语句则会由解析器进行语法语义解析,并生成“解析树”。
③解析器(Parser)将客户端发送的SQL进行语法解析,生成"解析树"。预处理器根据一些MySQL规则进一步检查“解析树”是否合法,例如这里将检查数据表和数据列是否存在,还会解析名字和别名,看看它们是否有歧义,最后生成新的“解析树”。
④查询优化器(Optimizer)根据“解析树”生成最优的执行计划。MySQL使用很多优化策略生成最优的执行计划,可以分为两类:静态优化(编译时优化)、动态优化(运行时优化)。
⑤查询执行引擎负责执行 SQL 语句,此时查询执行引擎会根据 SQL 语句中表的存储引擎类型,以及对应的API接口与底层存储引擎缓存或者物理文件的交互,得到查询结果并返回给客户端。若开启用查询缓存,这时会将SQL 语句和结果完整地保存到查询缓存(Cache&Buffer)中,以后若有相同的 SQL 语句执行则直接返回结果。
负责MySQL中的数据的存储和提取,是与文件打交道的
子系统,它是根据MySQL提供的文件访问层抽象接口定制的一种文件访问机制,这种机制就叫作存储引擎
(引擎了解)
InnoDB:支持事务,具有提交,回滚和崩溃恢复能力,事务安全
MyISAM:不支持事务和外键,访问速度快
Memory:利用内存创建表,访问速度非常快,因为数据在内存,而且默认使用Hash索引,但是一旦关闭,数据就会丢失
Archive:归档类型引擎,仅能支持insert和select语句
Csv:以CSV文件进行数据存储,由于文件限制,所有列必须强制指定not null,另外CSV引擎也不支持索引和分区,适合做数据交换的中间表
BlackHole: 黑洞,只进不出,进来消失,所有插入数据都不会保存
Federated:可以访问远端MySQL数据库中的表。一个本地表,不保存数据,访问远程表内容。
MRG_MyISAM:一组MyISAM表的组合,这些MyISAM表必须结构相同,Merge表本身没有数据,对Merge操作可以对一组MyISAM表进行操作。
1事务和外键
InnoDB支持事务和外键,具有安全性和完整性,适合大量insert或update操作
MyISAM不支持事务和外键,它提供高速存储和检索,适合大量的select查询操作
2锁机制
InnoDB支持行级锁,锁定指定记录。基于索引来加锁实现。
MyISAM支持表级锁,锁定整张表。
3索引结构
InnoDB使用聚集索引(聚簇索引),索引和记录在一起存储,既缓存索引,也缓存记录。
MyISAM使用非聚集索引(非聚簇索引),索引和记录分开。
4并发处理能力
MyISAM使用表锁,会导致写操作并发率低,读之间并不阻塞,读写阻塞。
InnoDB读写阻塞可以与隔离级别有关,可以采用多版本并发控制(MVCC)来支持高并发
5存储文件
InnoDB表对应两个文件,一个.frm表结构文件,一个.ibd数据文件。InnoDB表最大支持64TB;
MyISAM表对应三个文件,一个.frm表结构文件,一个MYD表数据文件,一个.MYI索引文件。从MySQL5.0开始默认限制是256TB。
引擎适用场景和选择
MyISAM
不需要事务支持(不支持)
并发相对较低(锁定机制问题)
数据修改相对较少,以读为主
数据一致性要求不高
InnoDB
需要事务支持(具有较好的事务特性)
行级锁定对高并发有很好的适应能力
数据更新较为频繁的场景
数据一致性要求较高
硬件设备内存较大,可以利用InnoDB较好的缓存能力来提高内存利用率,减少磁盘IO
总结
两种引擎该如何选择?
是否需要事务?有,InnoDB
是否存在并发修改?有,InnoDB
是否追求快速查询,且数据修改少?是,MyISAM
在绝大多数情况下,推荐使用InnoDB
从MySQL 5.5版本开始默认使用InnoDB作为引擎,它擅长处理事务,具有自动崩溃恢复的特性
主要分为内存结构和磁盘结构两大部分
内存结构:主要包括1.Buffer Pool、2.Change Buffer、3.Adaptive Hash Index和4.Log Buffer四大组件。
磁盘结构:1Tablespaces,2InnoDB Data Dictionary,3Doublewrite Buffer、4Redo Log
和Undo Logs。
1.IO Thread:
大量的AIO(Async IO)来做读写处理,这样可以极大提高数据库的性能
2.Purge Thread:
事务提交之后,其使用的undo日志将不再需要,因此需要Purge Thread回收已经分配的undo页。
3.Page Cleaner Thread:
将脏数据刷新到磁盘,同步数据,又能达到redo log循环使用的目的
4.Master Thread:
负责调度其他各线程,优先级最高。作用是将缓冲池中的数据异步刷新到磁盘 ,保证数据的一致性。包含:脏页的刷新(page cleaner thread)、undo页回收(purge thread)、redo日志刷新(log thread)、合并写缓冲等
1 InnoDB文件存储结构
InnoDB数据文件存储结构(重要):
分为一个ibd数据文件-->Segment(段)-->Extent(区)-->Page(页)-->Row(行)
Tablesapce
表空间,用于存储多个ibd数据文件,用于存储表的记录和索引。一个文件包含多个段。
Segment
段,用于管理多个Extent,分为数据段(Leaf node segment)、索引段(Non-leaf node
segment)、回滚段(Rollback segment)。一个表至少会有两个segment,一个管理数
据,一个管理索引。每多创建一个索引,会多两个segment。
Extent
区,一个区固定包含64个连续的页,大小为1M。当表空间不足,需要分配新的页资源,不会一页一页分,直接分配一个区。
Page
页,用于存储多个Row行记录,大小为16K。包含很多种页类型,比如数据页,undo页,系统页,事务数据页,大的BLOB对象页。
Row
行,包含了记录的字段值,事务ID(Trx id)、滚动指针(Roll pointer)、字段指针(Field
pointers)等信息。
3.5.1 Undo Log介绍
Undo:意为撤销或取消,以撤销操作为目的,返回指定某个状态的操作。
Undo Log:数据库事务开始之前,会将要修改的记录存放到 Undo 日志里,当事务回滚时或者数据库崩溃时,可以利用 Undo 日志,撤销未提交事务对数据库产生的影响。
Undo Log产生和销毁:Undo Log在事务开始前产生;事务在提交时,并不会立刻删除undo
log,innodb会将该事务对应的undo log放入到删除列表中,后面会通过后台线程purge thread进行回收处理。Undo Log属于逻辑日志,记录一个变化过程。例如执行一个delete,undolog会记录一个insert;执行一个update,undolog会记录一个相反的update。
Undo Log存储:undo log采用段的方式管理和记录。在innodb数据文件中包含一种rollback
segment回滚段,内部包含1024个undo log segment。可以通过下面一组参数来控制Undo log存储。
3.5.2 Undo Log作用
实现事务的原子性
Undo Log 是为了实现事务的原子性而出现的产物。事务处理过程中,如果出现了错误或者用户执行了 ROLLBACK 语句,MySQL 可以利用 Undo Log 中的备份将数据恢复到事务开始之前的状态。
实现多版本并发控制(MVCC)
Undo Log 在 MySQL InnoDB 存储引擎中用来实现多版本并发控制。事务未提交之前,Undo Log保存了未提交之前的版本数据,Undo Log 中的数据可作为数据旧版本快照供其他并发事务进行快照读。
事务A手动开启事务,执行更新操作,首先会把更新命中的数据备份到 Undo Buffer 中。
事务B手动开启事务,执行查询操作,会读取 Undo 日志数据返回,进行快照读
Redo:顾名思义就是重做。以恢复操作为目的,在数据库发生意外时重现操作。
3.6.1Redo Log:
指事务中修改的任何数据,将最新的数据备份存储的位置(Redo Log),被称为重做日志。
Redo Log 的生成和释放:随着事务操作的执行,就会生成Redo Log,在事务提交时会将产生Redo Log写入Log Buffer,并不是随着事务的提交就立刻写入磁盘文件。等事务操作的脏页写入到磁盘之后,Redo Log 的使命也就完成了,Redo Log占用的空间就可以重用(被覆盖写入)。
Redo Log工作原理
Redo Log 是为了实现事务的持久性而出现的产物。防止在发生故障的时间点,尚有脏页未写入表的 IBD 文件中,在重启 MySQL 服务的时候,根据 Redo Log 进行重做,从而达到事务的未入磁盘数据进行持久化这一特性。
Redo Buffer 持久化到 Redo Log 的策略,可通过 Innodb_flush_log_at_trx_commit 设置:
0:每秒提交 Redo buffer ->OS cache -> flush cache to disk,可能丢失一秒内的事务数
据。由后台Master线程每隔 1秒执行一次操作。
1(默认值):每次事务提交执行 Redo Buffer -> OS cache -> flush cache to disk,最安
全,性能最差的方式。
2:每次事务提交执行 Redo Buffer -> OS cache,然后由后台Master线程再每隔1秒执行OS
cache -> flush cache to disk 的操作。
一般建议选择取值2,因为 MySQL 挂了数据没有损失,整个服务器挂了才会损失1秒的事务提交数据。
3.6.2 Binlog日志
Binlog记录模式
Redo Log 是属于InnoDB引擎所特有的日志,而MySQL Server也有自己的日志,即 Binary
log(二进制日志),简称Binlog。Binlog是记录所有数据库表结构变更以及表数据修改的二进制日志,不会记录SELECT和SHOW这类操作。Binlog日志是以事件形式记录,还包含语句所执行的消耗时间。开启Binlog日志有以下两个最重要的使用场景
主从复制:在主库中开启Binlog功能,这样主库就可以把Binlog传递给从库,从库拿到
Binlog后实现数据恢复达到主从数据一致性。
数据恢复:通过mysqlbinlog工具来恢复数据。
文件记录模式有STATEMENT、ROW和MIXED三种,具体含义如下。
ROW(row-based replication, RBR):
日志中会记录每一行数据被修改的情况,然后在slave端对相同的数据进行修改。
优点:能清楚记录每一个行数据的修改细节,能完全实现主从数据同步和数据的恢复。
缺点:批量操作,会产生大量的日志,尤其是alter table会让日志暴涨。
STATMENT(statement-based replication, SBR):
每一条被修改数据的SQL都会记录到master的Binlog中,slave在复制的时候SQL进程会解析成和原来master端执行过的相同的SQL再次执行。简称SQL语句复制。
优点:日志量小,减少磁盘IO,提升存储和恢复速度
缺点:在某些情况下会导致主从数据不一致,比如last_insert_id()、now()等函数
MIXED(mixed-based replication, MBR):
以上两种模式的混合使用,一般会使用
STATEMENT模式保存binlog,对于STATEMENT模式无法复制的操作使用ROW模式保存
binlog,MySQL会根据执行的SQL语句选择写入模式。
Binlog写入机制
根据记录模式和操作触发event事件生成log event(事件触发执行机制)
将事务执行过程中产生log event写入缓冲区,每个事务线程都有一个缓冲区
Log Event保存在一个binlog_cache_mngr数据结构中,在该结构中有两个缓冲区,一个是
stmt_cache,用于存放不支持事务的信息;另一个是trx_cache,用于存放支持事务的信息。
事务在提交阶段会将产生的log event写入到外部binlog文件中。
不同事务以串行方式将log event写入binlog文件中,所以一个事务包含的log event信息在
binlog文件中是连续的,中间不会插入其他事务的log event
Redo Log和Binlog区别
Redo Log是属于InnoDB引擎功能,Binlog是属于MySQL Server自带功能,并且是以二进制
文件记录。
Redo Log属于物理日志,记录该数据页更新状态内容,Binlog是逻辑日志,记录更新过程。
Redo Log日志是循环写,日志空间大小是固定,Binlog是追加写入,写完一个写下一个,不
会覆盖使用
Redo Log作为服务器异常宕机后事务数据自动恢复使用,Binlog可以作为主从复制和数据恢
复使用。Binlog没有自动crash-safe能力。
这是最基本的索引类型,基于普通字段建立的索引,没有任何限制
与"普通索引"类似,不同的就是:索引字段的值必须唯一,但允许有空值 。在创建或修改表时追加唯一约束,就会自动创建对应的唯一索引。
它是一种特殊的唯一索引,不允许有空值。在创建或修改表时追加主键约束即可,每个表只能有一个主
键。
单一索引是指索引列为一列的情况,即新建索引的语句只实施在一列上;用户可以在多个列上建立索引,这种索引叫做组复合索引(组合索引)。复合索引可以代替多个单一索引,相比多个单一索引复合索引所需的开销更小。
查询操作在数据量比较少时,可以使用like模糊查询,但是对于大量的文本数据检索,效率很低。如果使用全文索引,查询速度会比like快很多倍。在MySQL 5.6 以前的版本,只有MyISAM存储引擎支持全文索引,从MySQL 5.6开始MyISAM和InnoDB存储引擎均支持。
索引的定义:是存储引擎用于快速查找记录的一种数据结构。需要额外开辟空间和数据维护
工作。
索引是物理数据页存储,在数据文件中(InnoDB,ibd文件),利用数据页(page)存储。
索引可以加快检索速度,但是同时也会降低增删改操作速度,索引维护需要代价。
索引涉及的理论知识:二分查找法、Hash和B+Tree。
二分查找法也叫作折半查找法,它是在有序数组中查找指定数据的搜索算法。它的优点是等值查询、范围查询性能优秀,缺点是更新数据、新增数据、删除数据维护成本高。
Hash底层实现是由Hash表来实现的,是根据键值 <key,value> 存储数据的结构。非常适合根据key查找value值,也就是单个key查询,或者说等值查询。
Hash索引可以方便的提供等值查询,但是对于范围查询就需要全表扫描了。
Hash索引在MySQL 中Hash结构主要应用在Memory原生的Hash索引 、InnoDB 自适应哈希索引.
InnoDB自适应哈希索引是为了提升查询效率,InnoDB存储引擎会监控表上各个索引页的查询,当InnoDB注意到某些索引值访问非常频繁时,会在内存中基于B+Tree索引再创建一个哈希索引,使得内存中的 B+Tree 索引具备哈希索引的功能,即能够快速定值访问频繁访问的索引页。
InnoDB自适应哈希索引:在使用Hash索引访问时,一次性查找就能定位数据,等值查询效率要优于B+Tree。自适应哈希索引的建立使得InnoDB存储引擎能自动根据索引页访问的频率和模式自动地为某些热点页建立哈希索引来加速访问。另外InnoDB自适应哈希索引的功能,用户只能选择开启或关闭功能,无法进行人工干涉
MySQL数据库索引采用的是B+Tree结构,在B-Tree结构上做了优化改造。
B-Tree结构(重要)
索引值和data数据分布在整棵树结构中
每个节点可以存放多个索引值及对应的data数据
树节点中的多个索引值从左到右升序排列
B树的搜索:从根节点开始,对节点内的索引值序列采用二分法查找,如果命中就结束查找。没有命中会进入子节点重复查找过程,直到所对应的的节点指针为空,或已经是叶子节点了才结束。
B+Tree结构
非叶子节点不存储data数据,只存储索引值,这样便于存储更多的索引值
叶子节点包含了所有的索引值和data数据
叶子节点用指针连接,提高区间的访问性能
相比B树,B+树进行范围查找时,只需要查找定位两个节点的索引值,然后利用叶子节点的指针进行遍历即可。而B树需要遍历范围内所有的节点和数据,显然B+Tree效率高。
聚簇索引和非聚簇索引:B+Tree的叶子节点存放主键索引值和行记录就属于聚簇索引;如果索引值和行记录分开存放就属于非聚簇索引。
主键索引和辅助索引:B+Tree的叶子节点存放的是主键字段值就属于主键索引;如果存放的是非主键值就属于辅助索引(二级索引)。
在InnoDB引擎中,主键索引采用的就是聚簇索引结构存储。
聚簇索引(聚集索引)
InnoDB的聚簇索引就是按照主键顺序构建 B+Tree结构。
B+Tree的叶子节点就是行记录,行记录和主键值紧凑地存储在一起。 这也意味着 InnoDB 的主键索引就是数据表本身,它按主键顺序存放了整张表的数据,占用的空间就是整个表数据量的大小。通常说的主键索引就是聚集索引。
InnoDB的表要求必须要有聚簇索引:
如果表定义了主键,则主键索引就是聚簇索引
如果表没有定义主键,则第一个非空unique列作为聚簇索引
否则InnoDB会从建一个隐藏的row-id作为聚簇索引
InnoDB辅助索引,也叫作二级索引,是根据索引列构建 B+Tree结构。但在 B+Tree 的叶子节点中只存了索引列和主键的信息。二级索引占用的空间会比聚簇索引小很多, 通常创建辅助索引就是为了提升查询效率。一个表InnoDB只能创建一个聚簇索引,但可以创建多个辅助索引。
非聚簇索引
与InnoDB表存储不同,MyISAM数据表的索引文件和数据文件是分开的,被称为非聚簇索引结构。
select_type
表示查询的类型。常用的值如下:
SIMPLE : 表示查询语句不包含子查询或union
PRIMARY:表示此查询是最外层的查询
UNION:表示此查询是UNION的第二个或后续的查询
DEPENDENT UNION:UNION中的第二个或后续的查询语句,使用了外面查询结果
UNION RESULT:UNION的结果
SUBQUERY:SELECT子查询语句
DEPENDENT SUBQUERY:SELECT子查询语句依赖外层查询的结果。
最常见的查询类型是SIMPLE,表示我们的查询没有子查询也没用到UNION查询
type
表示存储引擎查询数据时采用的方式。比较重要的一个属性,通过它可以判断出查询是全表扫描还是基于索引的部分扫描。常用属性值如下,从上至下效率依次增强。
ALL:表示全表扫描,性能最差。
index:表示基于索引的全表扫描,先扫描索引再扫描全表数据。
range:表示使用索引范围查询。使用>、>=、<、<=、in等等。
ref:表示使用非唯一索引进行单值查询。
eq_ref:一般情况下出现在多表join查询,表示前面表的每一个记录,都只能匹配后面表的一行结果。
const:表示使用主键或唯一索引做等值查询,常量查询。
NULL:表示不用访问表,速度最快。
possible_keys
表示查询时能够使用到的索引。注意并不一定会真正使用,显示的是索引名称。
key
表示查询时真正使用到的索引,显示的是索引名称。
rows
MySQL查询优化器会根据统计信息,估算SQL要查询到结果需要扫描多少行记录。原则上rows是越少效率越高,可以直观的了解到SQL效率高低。
key_len
表示查询使用了索引的字节数量。可以判断是否全部使用了组合索引。
key_len的计算规则如下:
字符串类型
字符串长度跟字符集有关:latin1=1、gbk=2、utf8=3、utf8mb4=4
char(n):n*字符集长度
varchar(n):n * 字符集长度 + 2字节
数值类型
TINYINT:1个字节
SMALLINT:2个字节
MEDIUMINT:3个字节
INT、FLOAT:4个字节
BIGINT、DOUBLE:8个字节
时间类型
DATE:3个字节
TIMESTAMP:4个字节
DATETIME:8个字节
字段属性
NULL属性占用1个字节,如果一个字段设置了NOT NULL,则没有此项。
Extra
Extra表示很多额外的信息,各种操作会在Extra提示相关信息,常见几种如下:
Using where
表示查询需要通过索引回表查询数据。
Using index
表示查询需要通过索引,索引就可以满足所需数据。
Using filesort
表示查询出来的结果需要额外排序,数据量小在内存,大的话在磁盘,因此有Using filesort
建议优化。
Using temprorary
查询使用到了临时表,一般出现于去重、分组等操作。
InnoDB索引有聚簇索引和辅助索引。聚簇索引的叶子节点存储行记录,InnoDB必须要有,且只有一个。辅助索引的叶子节点存储的是主键值和索引字段值,通过辅助索引无法直接定位行记录,通常情况下,需要扫码两遍索引树。先通过辅助索引定位主键值,然后再通过聚簇索引定位行记录,这就叫做回表查询,它的性能比扫一遍索引树低。
总结:通过索引查询主键值,然后再去聚簇索引查询记录信息
只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快,这就叫做索引覆盖。
即explain的输出结果Extra字段为Using index时,能够触发索引覆盖
实现索引覆盖最常见的方法就是:将被查询的字段,建立到组合索引。
复合索引使用时遵循最左前缀原则,最左前缀顾名思义,就是最左优先,即查询中使用到最左边的列,那么查询就会使用到索引,如果从索引的第二列开始查找,索引将失效。
面试题:MySQL在使用like模糊查询时,索引能不能起作用?
回答:MySQL在使用Like模糊查询时,索引是可以被使用的,只有把%字符写在后面才会使用到索引。
面试题:如果MySQL表的某一列含有NULL值,那么包含该列的索引是否有效?
对MySQL来说,NULL是一个特殊的值,从概念上讲,NULL意味着“一个未知值”,它的处理方式与其他值有些不同。比如:不能使用=,<,>这样的运算符,对NULL做算术运算的结果都是NULL,count时不会包括NULL行等,NULL比空字符串需要更多的存储空间等。
虽然MySQL可以在含有NULL的列上使用索引,但NULL和其他数据还是有区别的。最好设置NOT NULL,并给一个默认值,比如0和 ‘’ 空字符串等,如果是datetime类型,也可以设置系统当前时间或某个固定的特殊值,例如'1970-01-01 00:00:00'。
MySQL查询支持filesort和index两种方式的排序,filesort是先把结果查出,然后在缓存或磁盘进行排序操作,效率较低。使用index是指利用索引自动实现排序,不需另做排序操作,效率会比较高。
filesort有两种排序算法:双路排序和单路排序。
双路排序:需要两次磁盘扫描读取,最终得到用户数据。第一次将排序字段读取出来,然后排序;第二次去读取其他字段数据。
单路排序:从磁盘查询所需的所有列数据,然后在内存排序将结果返回。如果查询数据超出缓存sort_buffer,会导致多次磁盘读取操作,并创建临时表,最后产生了多次IO,反而会增加负担。
解决方案:少使用select *;增加sort_buffer_size容量和max_length_for_sort_data容量。
SHOW VARIABLES LIKE 'slow_query_log%'
查看慢查询日志
使用mysqldumpslow查看
MySQL 提供了一个慢查询日志分析工具mysqldumpslow,可以通过该工具分析慢查询日志
内容。在 MySQL bin目录下执行下面命令可以查看该使用格式。
索引和慢查询
使用索引时,不要只关注是否起作用,应该关心索引是否减少了查询扫描的数据行数,如果
扫描行数减少了,效率才会得到提升。对于一个大表,不止要创建索引,还要考虑索引过滤性,过滤性好,执行速度才会快。
提高索引过滤性
慢查询原因总结
全表扫描:explain分析type属性all
全索引扫描:explain分析type属性index
索引过滤性不好:靠索引字段选型、数据量和状态、表设计
频繁的回表查询开销:尽量少用select *,使用覆盖索引
偏移量是第一个参数:limit offset,pagesize
Select * from tableName limit ((currentPage-1)*pageSize,pageSize)
结果:在查询记录时,如果查询记录量相同,偏移量超过100后就开始随着偏移量增大,查询时间急剧的增加。(这种分页查询机制,每次都会从数据库第一条记录开始扫描,越往后查询越慢,而且查询的数据越多,也会拖慢总查询速度。)
第一步:利用覆盖索引优化
select * from user limit 10000,100;
select id from user limit 10000,100;
第二步:利用子查询优化
select * from user limit 10000,100;
select * from user where id>= (select id from user limit 10000,1) limit 100;
原因:使用了id做主键比较(id>=),并且子查询使用了覆盖索引进行优化。
关系型数据库管理系统中,一个逻辑工作单元要成为事务,必须满足这 4 个特性,即所谓的 ACID:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)
1.1 原子性
原子性:事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
修改---》Buffer Pool修改---》刷盘。可能会有下面两种情况:
事务提交了,如果此时Buffer Pool的脏页没有刷盘,如何保证修改的数据生效? Redo
如果事务没提交,但是Buffer Pool的脏页刷盘了,如何保证不该存在的数据撤销?Undo
1.2 持久性
持久性:指的是一个事务一旦提交,它对数据库中数据的改变就应该是永久性的,后续的操作或故障不应该对其有任何影响,不会丢失。
一个“提交”动作触发的操作有:binlog落地、发送binlog、存储引擎提交、flush_logs,
check_point、事务提交标记等。这些都是数据库保证其数据完整性、持久性的手段
MySQL的持久性也与WAL技术相关,redo log在系统Crash重启之类的情况时,可以修复数据,从而保障事务的持久性。通过原子性可以保证逻辑上的持久性,通过存储引擎的数据刷盘可以保证物理上的持久性。
1.3 隔离性
隔离性:指的是一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对其他的并发事务是隔离的。
InnoDB 支持的隔离性有4种,隔离性从低到高分别为:读未提交、读提交、可重复读、可串行化。锁和多版本控制(MVCC)技术就是用于保障隔离性的
1.4 一致性
一致性:指的是事务开始之前和事务结束之后,数据库的完整性限制未被破坏。一致性包括两方面的内容,分别是约束一致性和数据一致性。
约束一致性:创建表结构时所指定的外键、Check、唯一索引等约束,可惜在 MySQL 中不支持Check 。
数据一致性:是一个综合性的规定,因为它是由原子性、持久性、隔离性共同保证的结果,而不是单单依赖于某一种技术。
事务并发处理可能带来一些问题,比如:更新丢失、脏读、不可重复读、幻读等。
更新丢失
当两个或多个事务更新同一行记录,会产生更新丢失现象。可以分为回滚覆盖和提交覆盖。
回滚覆盖:一个事务回滚操作,把其他事务已提交的数据给覆盖了。
提交覆盖:一个事务提交操作,把其他事务已提交的数据给覆盖了。
脏读
一个事务读取到了另一个事务修改但未提交的数据。
不可重复读
一个事务中多次读取同一行记录不一致,后面读取的跟前面读取的不一致。
幻读
一个事务中多次按相同条件查询,结果不一致。后续查询的结果和面前查询结果不同,多了或少了几行记录。
最简单的方法,就是完全顺序执行所有事务的数据库操作,不需要加锁,简单的说就是全局排队。序列化执行所有的事务单元,数据库某个时刻只处理一个事务操作,特点是强一致性,处理性能低。
引入锁之后就可以支持并发处理事务,如果事务之间涉及到相同的数据项时,会使用排他锁,或叫互斥锁,先进入的事务独占数据项以后,其他事务被阻塞,等待前面的事务释放锁。
读和写操作:读读、写写、读写、写读。
读写锁就是进一步细化锁的颗粒度,区分读操作和写操作,让读和读之间不加锁,这样下面的两个事务就可以同时被执行了。
作用:读写锁,可以让读和读并行,而读和写、写和读、写和写这几种之间还是要加排他锁。
(重要)
多版本控制MVCC,也就是Copy on Write的思想。
作用:MVCC除了支持读和读并行,还支持读和写、写和读的并行,但为了保证一致性,写和写是无法并行的。
(写读并行)
原理:在事务1开始写操作的时候会copy一个记录的副本,其他事务读操作会读取这个记录副本,因此不会影响其他事务对此记录的读取,实现写和读并行。
MVCC(Multi Version Concurrency Control)被称为多版本控制,是指在数据库中为了实现高并发的数据访问,对数据进行多版本处理,并通过事务的可见性来保证事务能看到自己应该看到的数据版本。
多版本控制很巧妙地将稀缺资源的独占互斥转换为并发,大大提高了数据库的吞吐量及读写性能。
作用:多版本控制MVCC,也就是Copy on Write的思想。MVCC除了支持读和读并行,还支持读和写、写和读的并行,但为了保证一致性,写和写是无法并行的。
如何生成的多版本?每次事务修改操作之前,都会在Undo日志中记录修改之前的数据状态和事务号,该备份记录可以用于其他事务的读取,也可以进行必要时的数据回滚。
二、MVCC实现原理
MVCC最大的好处是读不加锁,读写不冲突。在读多写少的系统应用中,读写不冲突是非常重要的,极大的提升系统的并发性能,这也是为什么现阶段几乎所有的关系型数据库都支持 MVCC 的原因,不过目前MVCC只在 Read Commited 和 Repeatable Read 两种隔离级别下工作。
在 MVCC 并发控制中,读操作可以分为两类: 快照读(Snapshot Read)与当前读 (Current Read)。
快照读:读取的是记录的快照版本(有可能是历史版本),不用加锁。(select)
当前读:(解决读写冲突)读取的是记录的最新版本,并且当前读返回的记录,都会加锁,保证其他事务不会再并发修改这条记录。(select... for update 或lock in share mode,insert/delete/update)
MVCC行更新过程:(解决写读冲突)
1.用排他锁锁定该行;记录 Redo log;
2.把该行修改前的值复制到 Undo log;
3.修改当前行的值,填写事务编号,使回滚指针指向 Undo log 中修改前的行
MVCC已经实现了读读、读写、写读并发处理,如果想进一步解决写写冲突,可以采用下面两种方案:
乐观锁
悲观锁
3.1 隔离级别类型
读未提交
解决了回滚覆盖类型的更新丢失,但可能发生脏读现象
已提交读
Read Committed 读已提交:只能读取到其他会话中已经提交的数据,解决了脏读。但可能发生不可重复读现象,也就是可能在一个事务中两次查询结果不一致。
可重复度
Repeatable Read 可重复读:解决了不可重复读,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上会出现幻读,简单的说幻读指的的当用户读取某一范围的数据行时,另一个事务又在该范围插入了新行,当用户在读取该范围的数据时会发现有新的幻影行
可串行化
Serializable 串行化:所有的增删改查串行执行。它通过强制事务排序,解决相互冲突,从而解决幻度的问题。这个级别可能导致大量的超时现象的和锁竞争,效率低下。
可以根据系统特点来选择一个合适的隔离级别,
比如对不可重复读和幻读并不敏感,更多关心数据库并发处理能力,此时可以使用Read Commited隔离级别。
事务隔离级别,针对Innodb引擎,支持事务的功能。像MyISAM引擎没有关系。
事务隔离级别和锁的关系:
事务隔离级别本质上是对锁和MVCC使用的封装,隐藏了底层细节。
MySQL默认隔离级别:可重复读
Oracle、SQLServer默认隔离级别:读已提交
一般使用时,建议采用默认隔离级别,然后存在的一些并发问题,可以通过悲观锁、乐观锁等实现处理。
在 MySQL中锁有很多不同的分类,下边从不同维度对锁进行分类
1 从操作的粒度可分为表级锁、行级锁和页级锁
表级锁:每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在
MyISAM、InnoDB、BDB 等存储引擎中。
行级锁:每次操作锁住一行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应
用在InnoDB 存储引擎中。
页级锁:每次锁定相邻的一组记录,锁定粒度界于表锁和行锁之间,开销和加锁时间界于表
锁和行锁之间,并发度一般。应用在BDB 存储引擎中。
2 从操作的类型可分为读锁和写锁
读锁(S锁):共享锁,针对同一份数据,多个读操作可以同时进行而不会互相影响。
写锁(X锁):排他锁,当前写操作没有完成前,它会阻断其他写锁和读锁。
IS锁、IX锁:意向读锁、意向写锁,属于表级锁,S和X主要针对行级锁。在对表记录添加S或X锁之前,会先对表添加IS或IX锁。
S锁:事务A对记录添加了S锁,可以对记录进行读操作,不能做修改,其他事务可以对该记录追加S锁,但是不能追加X锁,需要追加X锁,需要等记录的S锁全部释放。
X锁:事务A对记录添加了X锁,可以对记录进行读和修改操作,其他事务不能对记录做读和修改操作。
3 从操作的性能可分为乐观锁和悲观锁
乐观锁:一般的实现方式是对记录数据版本进行比对,在数据更新提交的时候才会进行冲突
检测,如果发现冲突了,则提示错误信息。
悲观锁:在对一条数据修改的时候,为了避免同时被其他人修改,在修改数据之前先锁定,
再修改的控制方式。共享锁和排他锁是悲观锁的不同实现,但都属于悲观锁范畴。
在InnoDB引擎中,我们可以使用行锁和表锁,其中行锁又分为共享锁和排他锁。
InnoDB行锁是通过对索引数据页上的记录加锁实现的,主要实现算法有 3 种:Record Lock、Gap Lock 和 Next-key Lock。
RecordLock锁:锁定单个行记录的锁。(记录锁,RC、RR隔离级别都支持)
GapLock锁:间隙锁,锁定索引记录间隙,确保索引记录的间隙不变。(范围锁,RR隔离级别支持)
Next-key Lock 锁:记录锁和间隙锁组合,同时锁住数据,并且锁住数据前后范围。(记录锁+范围锁,RR隔离级别支持)
在RR隔离级别,InnoDB对于记录加锁行为都是先采用Next-Key Lock,但是当SQL操作含有唯一索引时,Innodb会对Next-Key Lock进行优化,降级为RecordLock,仅锁住索引本身而非范围。
在集群架构设计时,主要遵从下面三个维度:
可用性
扩展性
一致性
站点高可用,冗余站点
服务高可用,冗余服务
数据高可用,冗余数据
保证高可用的方法是冗余。但是数据冗余带来的问题是数据一致性问题。
实现高可用的方案有以下几种架构模式:
主从模式
简单灵活,能满足多种需求。比较主流的用法,但是写操作高可用需要自行处理。
双主模式
互为主从,有双主双写、双主单写两种方式,建议使用双主单写
扩展性主要围绕着读操作扩展和写操作扩展展开。
如何扩展以提高读性能
加从库
简单易操作,方案成熟。
从库过多会引发主库性能损耗。建议不要作为长期的扩充方案,应该设法用良好的设计避免
持续加从库来缓解读性能问题。
分库分表
可以分为垂直拆分和水平拆分,垂直拆分可以缓解部分压力,水平拆分理论上可以无限扩
展。
如何扩展以提高写性能
分库分表
一致性主要考虑集群中各数据库数据同步以及同步延迟问题。可以采用的方案如下:
不使用从库
扩展读性能问题需要单独考虑,否则容易出现系统瓶颈。
增加访问路由层
可以先得到主从同步最长时间t,在数据发生修改后的t时间内,先访问主库。
MySQL主从模式是指数据可以从一个MySQL数据库服务器主节点复制到一个或多个从节点。MySQL 默认采用异步复制方式,这样从节点不用一直访问主服务器来更新自己的数据,从节点可以复制主数据库中的所有数据库,或者特定的数据库,或者特定的表。
mysql主从复制用途:
实时灾备,用于故障切换(高可用)
读写分离,提供查询服务(读扩展)
数据备份,避免影响业务(高可用)
主从部署必要条件:
从库服务器能连通主库
主库开启binlog日志(设置log-bin参数)
主从server-id不同
2.2.1 主从复制
主从复制整体分为以下三个步骤:
主库将数据库的变更操作记录到Binlog日志文件中
从库读取主库中的Binlog日志文件信息写入到从库的Relay Log中继日志中
从库读取中继日志信息在从库中进行Replay,更新从库数据信息
Master服务器对数据库更改操作记录在Binlog中,BinlogDump Thread接到写入请求后,读取Binlog信息推送给Slave的I/O Thread。
Slave的I/O Thread将读取到的Binlog信息写入到本地Relay Log中。
Slave的SQL Thread检测到Relay Log的变更请求,解析relay log中内容在从库上执行。
上述过程都是异步操作,俗称异步复制,存在数据延迟现象
mysql主从复制存在的问题:
主库宕机后,数据可能丢失
从库只有一个SQL Thread,主库写压力大,复制很可能延时
解决方法:
半同步复制---解决数据丢失的问题
并行复制----解决从库复制延迟的问题
2.2.2 半同步复制
MySQL让Master在某一个时间点等待Slave节点的 ACK(Acknowledge
character)消息,接收到ACK消息后才进行事务提交,这也是半同步复制的基础
MySQL 事务写入碰到主从复制时的完整过程,主库事务写入分为 4个步骤
InnoDB Redo File Write (Prepare Write)
Binlog File Flush & Sync to Binlog File
InnoDB Redo File Commit(Commit Write)
Send Binlog to Slave
当Master不需要关注Slave是否接受到Binlog Event时,即为传统的主从复制。
当Master需要在第三步等待Slave返回ACK时,即为 after-commit,半同步复制(MySQL 5.5引入)。
当Master需要在第二步等待 Slave 返回 ACK 时,即为 after-sync,增强半同步(MySQL 5.7引入)
MySQL的主从复制延迟问题,MySQL从5.6版本开始追加了并行复制功能,目的就是为了改善复制延迟问题,并行复制称为enhanced multi-threaded slave(简称MTS)。
在从库中有两个线程IO Thread和SQL Thread,都是单线程模式工作,因此有了延迟问题,我们可以采用多线程机制来加强,减少从库复制延迟。(IO Thread多线程意义不大,主要指的是SQL Thread多线程)
在MySQL的5.6、5.7、8.0版本上,都是基于上述SQL Thread多线程思想,不断优化,减少复制延迟。
2.3.1 MySQL 5.6并行复制原理
MySQL 5.6版本也支持所谓的并行复制,但是其并行只是基于库的。如果用户的MySQL数据库中是多个库,对于从库复制的速度的确可以有比较大的帮助
2.3.2 MySQL 5.7并行复制原理
MySQL 5.7是基于组提交的并行复制,MySQL 5.7才可称为真正的并行复制,这其中最为主要的原因就是master服务器上是怎么并行执行的slave上就怎样进行并行回放。不再有库的并行复制限制。
MySQL 5.7中组提交的并行复制究竟是如何实现的?
MySQL 5.7是通过对事务进行分组,当事务提交时,它们将在单个操作中写入到二进制日志中。如果多个事务能同时提交成功,那么它们意味着没有冲突,因此可以在Slave上并行执行,所以通过在主库上的二进制日志中添加组提交信息。
MySQL 5.7的并行复制基于一个前提,即所有已经处于prepare阶段的事务,都是可以并行提交的。这些当然也可以在从库中并行提交,因为处理这个阶段的事务都是没有冲突的。在一个组里提交的事务,一定不会修改同一行。这是一种新的并行复制思路,完全摆脱了原来一直致力于为了防止冲突而做的分发算法,等待策略等复杂的而又效率底下的工作。
InnoDB事务提交采用的是两阶段提交模式。一个阶段是prepare,另一个是commit。
为了兼容MySQL 5.6基于库的并行复制,5.7引入了新的变量slave-parallel-type,其可以配置的值有:
DATABASE(默认值,基于库的并行复制方式)、LOGICAL_CLOCK(基于组提交的并行复制方式)。
那么如何知道事务是否在同一组中,生成的Binlog内容如何告诉Slave哪些事务是可以并行复制的?
在MySQL 5.7版本中,其设计方式是将组提交的信息存放在GTID中。为了避免用户没有开启GTID功能(gtid_mode=OFF),MySQL 5.7又引入了称之为Anonymous_Gtid的二进制日志event类型ANONYMOUS_GTID_LOG_EVENT。通过mysqlbinlog工具分析binlog日志,就可以发现组提交的内部信息。
可以发现MySQL 5.7二进制日志较之原来的二进制日志内容多了last_committed和
sequence_number,last_committed表示事务提交的时候,上次事务提交的编号,如果事务具有相同的last_committed,表示这些事务都在一组内,可以进行并行的回放。
2.3.3 MySQL8.0 并行复制
MySQL8.0 是基于write-set的并行复制。MySQL会有一个集合变量来存储事务修改的记录信息(主键哈希值),所有已经提交的事务所修改的主键值经过hash后都会与那个变量的集合进行对比,来判断改行是否与其冲突,并以此来确定依赖关系,没有冲突即可并行。这样的粒度,就到了 row级别了,此时并行的粒度更加精细,并行的速度会更快。
2.3.4 并行复制配置与调优
binlog_transaction_dependency_history_size
用于控制集合变量的大小。
binlog_transaction_depandency_tracking
用于控制binlog文件中事务之间的依赖关系,即last_committed值。
COMMIT_ORDERE: 基于组提交机制
WRITESET: 基于写集合机制
WRITESET_SESSION: 基于写集合,比writeset多了一个约束,同一个session中的事务
last_committed按先后顺序递增
transaction_write_set_extraction
用于控制事务的检测算法,参数值为:OFF、 XXHASH64、MURMUR32
master_info_repository
开启MTS功能后,务必将参数master_info_repostitory设置为TABLE,这样性能可以有50%~80%的提升。这是因为并行复制开启后对于元master.info这个文件的更新将会大幅提升,资源的竞争也会变大。
slave_parallel_workers
若将slave_parallel_workers设置为0,则MySQL 5.7退化为原单线程复制,但将
slave_parallel_workers设置为1,则SQL线程功能转化为coordinator线程,但是只有1个worker
线程进行回放,也是单线程复制。然而,这两种性能却又有一些的区别,因为多了一次
coordinator线程的转发,因此slave_parallel_workers=1的性能反而比0还要差。
slave_preserve_commit_order
MySQL 5.7后的MTS可以实现更小粒度的并行复制,但需要将slave_parallel_type设置为
LOGICAL_CLOCK,但仅仅设置为LOGICAL_CLOCK也会存在问题,因为此时在slave上应用事务的顺序是无序的,和relay log中记录的事务顺序不一样,这样数据一致性是无法保证的,为了保证事务是按照relay log中记录的顺序来回放,就需要开启参数slave_preserve_commit_order。
要开启enhanced multi-threaded slave其实很简单,只需根据如下设置:
2.4.1 读写分离引入时机
大多数互联网业务中,往往读多写少,这时候数据库的读会首先成为数据库的瓶颈。如果我们已经优化了SQL,但是读依旧还是瓶颈时,这时就可以选择“读写分离”架构了。
读写分离首先需要将数据库分为主从库,一个主库用于写数据,多个从库完成读数据的操作,主从库之间通过主从复制机制进行数据的同步,如图所示。
在应用中可以在从库追加多个索引来优化查询,主库这些索引可以不加,用于提升写效率。
读写分离架构也能够消除读写锁冲突从而提升数据库的读写性能。使用读写分离架构需要注意:主从同步延迟和读写分配机制问题
2.4.2 主从同步延迟
使用读写分离架构时,数据库主从同步具有延迟性,数据一致性会有影响,对于一些实时性要求比较高的操作,可以采用以下解决方案。
写后立刻读
在写入数据库后,某个时间段内读操作就去主库,之后读操作访问从库。
二次查询
先去从库读取数据,找不到时就去主库进行数据读取。该操作容易将读压力返还给主库,为了避免恶意攻击,建议对数据库访问API操作进行封装,有利于安全和低耦合。
根据业务特殊处理
根据业务特点和重要程度进行调整,比如重要的,实时性要求高的业务数据读写可以放在主库。对于次要的业务,实时性要求不高可以进行读写分离,查询时去从库查询。
2.4.3 读写分离落地
读写路由分配机制是实现读写分离架构最关键的一个环节,就是控制何时去主库写,何时去从库读。目前较为常见的实现方案分为以下两种:
基于编程和配置实现(应用端)
程序员在代码中封装数据库的操作,代码中可以根据操作类型进行路由分配,增删改时操作主库,查询时操作从库。这类方法也是目前生产环境下应用最广泛的。
优点是实现简单,因为程序在代码中实现,不需要增加额外的硬件开支,
缺点是需要开发人员来实现,运维人员无从下手,如果其中一个数据库宕机了,就需要修改配置重启项目。
基于服务器端代理实现(服务器端)
目前有很多性能不错的数据库中间件,常用的有MySQL Proxy、MyCat以及Shardingsphere等等。
MySQL Proxy:是官方提供的MySQL中间件产品可以实现负载平衡、读写分离等。
MyCat:MyCat是一款基于阿里开源产品Cobar而研发的,基于 Java 语言编写的开源数据库中间件。
ShardingSphere:ShardingSphere是一套开源的分布式数据库中间件解决方案,它由Sharding-
JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成。已经在2020
年4月16日从Apache孵化器毕业,成为Apache顶级项目。
Atlas:Atlas是由 Qihoo 360公司Web平台部基础架构团队开发维护的一个数据库中间件。
Amoeba:变形虫,该开源框架于2008年开始发布一款 Amoeba for MySQL软件。
3.1 适用场景
很多企业刚开始都是使用MySQL主从模式,一主多从、读写分离等。但是单主如果发生单点故障,从库切换成主库还需要作改动。因此,如果是双主或者多主,就会增加MySQL入口,提升了主库的可用性。
因此随着业务的发展,数据库架构可以由主从模式演变为双主模式。双主模式是指两台服务器互为主从,任何一台服务器数据变更,都会通过复制应用到另外一方的数据库中。
使用双主双写还是双主单写?
建议大家使用双主单写,因为双主双写存在以下问题:
ID冲突
在A主库写入,当A数据未同步到B主库时,对B主库写入,如果采用自动递增容易发生ID主键的冲突。
可以采用MySQL自身的自动增长步长来解决,例如A的主键为1,3,5,7...,B的主键为2,4,6,8... ,但是对数据库运维、扩展都不友好。
更新丢失
同一条记录在两个主库中进行更新,会发生前面覆盖后面的更新丢失。
高可用架构如下图所示,其中一个Master提供线上服务,另一个Master作为备胎供高可用切换,Master下游挂载Slave承担读请求。
3.2 MMM架构
MMM(Master-Master Replication Manager for MySQL)是一套用来管理和监控双主复制,支持双主故障切换的第三方软件。MMM 使用Perl语言开发,虽然是双主架构,但是业务上同一时间只允许一个节点进行写入操作。下图是基于MMM实现的双主高可用架构。
MMM故障处理机制
MMM 包含writer和reader两类角色,分别对应写节点和读节点。
当 writer节点出现故障,程序会自动移除该节点上的VIP
写操作切换到 Master2,并将Master2设置为writer将所有Slave节点会指向Master2
除了管理双主节点,MMM 也会管理 Slave 节点,在出现宕机、复制延迟或复制错误,MMM 会移除该节点的 VIP,直到节点恢复正常。
MMM监控机制
MMM 包含monitor和agent两类程序,功能如下:
monitor:监控集群内数据库的状态,在出现异常时发布切换命令,一般和数据库分开部
署。
agent:运行在每个 MySQL 服务器上的代理进程,monitor 命令的执行者,完成监控的探针
工作和具体服务设置,例如设置 VIP(虚拟IP)、指向新同步节
使用分库分表时,主要有垂直拆分和水平拆分两种拆分模式,都属于物理空间的拆分。
分库分表方案:只分库、只分表、分库又分表。
垂直拆分:由于表数量多导致的单个库大。将表拆分到多个库中。
水平拆分:由于表记录多导致的单个库大。将表记录拆分到多个表中。
4.1 拆分方式
应用时有垂直分库和垂直分表两种方式,一般谈到的垂直拆分主要指的是垂直分库。
垂直拆分优点:
拆分后业务清晰,拆分规则明确;
易于数据的维护和扩展;
可以使得行数据变小,一个数据块 (Block) 就能存放更多的数据,在查询时就会减少 I/O 次
数;
可以达到最大化利用 Cache 的目的,具体在垂直拆分的时候可以将不常变的字段放一起,将经常改变的放一起;
便于实现冷热分离的数据表设计模式。
垂直拆分缺点(是分库,或者表的列拆字段分):
主键出现冗余,需要管理冗余列;
会引起表连接 JOIN 操作,可以通过在业务服务器上进行 join 来减少数据库压力,提高了系统的复杂度;
依然存在单表数据量过大的问题;
事务处理复杂。
水平拆分(表的行拆分)
水平拆分又称为横向拆分。 相对于垂直拆分,它不再将数据根据业务逻辑分类,而是通过某个字段(或某几个字段),根据某种规则将数据分散至多个库或表中,每个表仅包含数据的一部分
水平拆分:解决表中记录过多问题。
垂直拆分:解决表过多或者是表字段过多问题。
水平拆分重点考虑拆分规则:例如范围、时间或Hash算法等。
水平拆分优点:
拆分规则设计好,join 操作基本可以数据库做;
不存在单库大数据,高并发的性能瓶颈;
切分的表的结构相同,应用层改造较少,只需要增加路由规则即可;
提高了系统的稳定性和负载能力。
水平拆分缺点:
拆分规则难以抽象;
跨库Join性能较差;
分片事务的一致性难以解决;
数据扩容的难度和维护量极大。
日常工作中,我们通常会同时使用两种拆分方式,垂直拆分更偏向于产品/业务/功能拆分的过程,在技
术上我们更关注水平拆分的方案。
4.2 主键策略
4.3 分片策略
4.3.1 分片概念
分片:表示分配过程,是一个逻辑上概念,表示如何实现
分库分表:表示分配结果,是一个物理上概念,表示最终实现的结果
数据库扩展方案:
横向扩展:一个库变多个库,加机器数量
纵向扩展:一个库还是一个库,优化机器性能,加高配CPU或内存
4.3.2 分片策略
数据分片是根据指定的分片键和分片策略将数据水平拆分,拆分成多个数据片后分散到多个数据存储节点中。分片键是用于划分和定位表的字段,一般使用ID或者时间字段。
1 基于范围分片
根据特定字段的范围进行拆分,比如用户ID、订单时间、产品价格等。例如:
{[1 - 100] => Cluster A, [101 - 199] => Cluster B}
优点:新的数据可以落在新的存储节点上,如果集群扩容,数据无需迁移。
缺点:数据热点分布不均,数据冷热不均匀,导致节点负荷不均。
2 哈希取模分片
整型的Key可直接对设备数量取模,其他类型的字段可以先计算Key的哈希值,然后再对设备数量取模。假设有n台设备,编号为0 ~ n-1,通过Hash(Key) % n就可以确定数据所在的设备编号。该模式也称为离散分片。
A:1,6,11,16,21
B:2,7,12,17,22
C:3,8,13,18,23
D:4,9,14,19,24
E:5,10,15,20,25
优点:实现简单,数据分配比较均匀,不容易出现冷热不均,负荷不均的情况。
缺点:扩容时会产生大量的数据迁移,比如从n台设备扩容到n+1,绝大部分数据需要重新分配和迁移。
3 一致性哈希分片
采用Hash取模的方式进行拆分,后期集群扩容需要迁移旧的数据。使用一致性Hash算法能够很大程度的避免这个问题,所以很多中间件的集群分片都会采用一致性Hash算法。
一致性Hash是将数据按照特征值映射到一个首尾相接的Hash环上,同时也将节点(按照IP地址或者机器名Hash)映射到这个环上。对于数据,从数据在环上的位置开始,顺时针找到的第一个节点即为数据的存储节点
一致性Hash在增加或者删除节点的时候,受到影响的数据是比较有限的,只会影响到Hash环相邻的节点,不会发生大规模的数据迁移。
4.4 扩容方案
横向扩展会有什么技术难度?
数据迁移问题
分片规则改变
数据同步、时间点、数据一致性
4.4.1 停机扩容
这是一种很多人初期都会使用的方案,尤其是初期只有几台数据库的时候。停机扩容的具体步骤如下:
站点发布一个公告,例如:“为了为广大用户提供更好的服务,本站点将在今晚00:00-2:00之间升级,给您带来不便抱歉";时间到了,停止所有对外服务;
新增n个数据库,然后写一个数据迁移程序,将原有x个库的数据导入到最新的y个库中。比如分片
规则由%x变为%y;
数据迁移完成,修改数据库服务配置,原来x个库的配置升级为y个库的配置
重启服务,连接新库重新对外提供服务
回滚方案:万一数据迁移失败,需要将配置和数据回滚,改天再挂公告。
优点:简单
缺点:
停止服务,缺乏高可用
程序员压力山大,需要在指定时间完成
如果有问题没有及时测试出来启动了服务,运行后发现问题,数据会丢失一部分,难以回滚。
适用场景:
小型网站
大部分游戏
对高可用要求不高的服务
4.4.2 平滑扩容
数据库扩容的过程中,如果想要持续对外提供服务,保证服务的可用性,平滑扩容方案是最好的选择。平滑扩容就是将数据库数量扩容成原来的2倍,比如:由2个数据库扩容到4个数据库,具体步骤如下:
新增2个数据库
配置双主进行数据同步(先测试、后上线)
数据同步完成之后,配置双主双写(同步因为有延迟,如果时时刻刻都有写和更新操作,会存在不准确问题)
数据同步完成后,删除双主同步,修改数据库配置,并重启;
此时已经扩容完成,但此时的数据并没有减少,新增的数据库跟旧的数据库一样多的数据,此时还需要写一个程序,清空数据库中多余的数据,如
平滑扩容方案能够实现n库扩2n库的平滑扩容,增加数据库服务能力,降低单库一半的数据量。其核心原理是:成倍扩容,避免数据迁移。
优点:
扩容期间,服务正常进行,保证高可用
相对停机扩容,时间长,项目组压力没那么大,出错率低
扩容期间遇到问题,随时解决,不怕影响线上服务
可以将每个数据库数据量减少一半
缺点:
程序复杂、配置双主同步、双主双写、检测数据同步等
后期数据库扩容,比如成千上万,代价比较高
适用场景:
大型网站
对高可用要求高的服务
数据库优化维度有四个:
硬件升级、系统配置、表结构设计、SQL语句及索引
优化选择:
优化成本:硬件升级>系统配置>表结构设计>SQL语句及索引。
优化效果:硬件升级<系统配置<表结构设计<SQL语句及索引。
扩大innodb_buffer_pool_size
默认情况,仅仅有某条数据被读取一次,才会缓存在 innodb_buffer_pool。
所以,数据库刚刚启动,须要进行数据预热,将磁盘上的全部数据缓存到内存中。
数据预热能够提高读取速度。
增大redolog,减少落盘次数
innodb_log_file_size 设置为 0.25 * innodb_buffer_pool_size
通用查询日志、慢查询日志可以不开 ,bin-log开
生产中不开通用查询日志,遇到性能问题开慢查询日志
写redolog策略 innodb_flush_log_at_trx_commit设置为0或2
如果不涉及非常高的安全性 (金融系统),或者基础架构足够安全,或者事务都非常小,都能够用 0或者 2 来减少磁盘操作
使用SSD或者内存磁盘
设计中间表,一般针对于统计分析功能,或者实时性不高的需求(OLTP、OLAP)
为减少关联查询,创建合理的冗余字段(创建冗余字段还需要注意数据一致性问题)
对于字段太多的大表,考虑拆表(比如一个表有100多个字段)
对于表中经常不被使用的字段或者存储数据比较多的字段,考虑拆表
每张表建议都要有一个主键(主键索引),而且主键类型最好是int类型,建议自增主键(不考虑分布式系统的情况下 雪花算法)。
engine='InnoDB'
) B USING (table_schema,table_name)
WHERE B.table_schema NOT IN ('information_schema','mysql')
ORDER BY table_schema,table_name,index_name,seq_in_index
) A
GROUP BY table_schema,table_name,index_name
) AA
ORDER BY db,tb;
mysql -uroot -proot -AN < /root/loadtomem.sql > /root/loadtomem.sql
mysql -uroot < /root/loadtomem.sql > /dev/null 2>&1
数据库中的表越小,在它上面执行的查询也就会越快。
因此,在创建表的时候,为了获得更好的性能,我们可以将表中字段的宽度设得尽可能小。
尽量把字段设置为NOTNULL,这样在将来执行查询的时候,数据库不用去比较NULL值。
对于某些文本字段,例如“省份”或者“性别”,我们可以将它们定义为ENUM类型。因为在MySQL中,ENUM类型被当作数值型数据来处理,而数值型数据被处理起来的速度要比文本类型快得多。这样,我们又可以提高数据库的性能。能用数字的用数值类型sex 1 0
type列,连接类型。一个好的SQL语句至少要达到range级别。杜绝出现all级别。 index
key列,使用到的索引名。如果没有选择索引,值是NULL。可以采取强制索引方式。
key_len列,索引长度。
rows列,扫描行数。该值是个预估值。
extra列,详细说明。注意,常见的不太友好的值,如下:Using filesort,Using temporary 。
常见的索引:
where 字段 、组合索引 (最左前缀) 、 索引下推 (非选择行不加锁) 、覆盖索引(不回表)on 两边、排序 、分组统计
MySQL对于IN做了相应的优化,即将IN中的常量全部存储在一个数组里面,而且这个数组是排好序
的。但是如果数值较多,产生的消耗也是比较大的。
SELECT * 增加很多不必要的消耗(CPU、IO、内存、网络带宽);减少了使用覆盖索引的可能性;当
表结构发生改变时,前端也需要更新。所以要求直接在select后面接上字段名。
3.4 当只需要一条数据的时候,使用limit 1
3.5 排序字段加索引
3.6 如果限制条件中其他字段没有索引,尽量少用or
or两边的字段中,如果有一个不是索引字段,会造成该查询不走索引的情况
可以换成union all
3.7 尽量用union all代替union
union和union all的差异主要是前者需要将结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的CPU运算,加大资源消耗及延迟。当然,union all的前提条件是两个结果集没有重复数据。
3.8 不使用ORDER BY RAND()
ORDER BY RAND() 不走索引
select * from tbiguser order by rand() limit 10;
select * from tbiguser t1 join (select rand()*(select max(id) from tbiguser) nid ) t2 on t1.id>t2.nid limit 10;
3.9 区分in和exists、not in和not exists
区分in和exists主要是造成了驱动顺序的改变(使用小表驱动大表),如果是exists,那么以外层表为驱动表,先被访问,如果是IN,那么先执行子查询。所以IN适合于外表大而内表小的情况;EXISTS适合于外表小而内表大的情况。
关于not in和not exists,推荐使用not exists,不仅仅是效率问题,not in可能存在逻辑问题。如何高效的写出一个替代not exists的SQL语句?
3.10 使用合理的分页方式以提高分页的效率
分页使用 limit m,n 尽量让m 小利用主键的定位,可以减小m的值
3.11 分段查询
一些用户选择页面中,可能一些用户选择的范围过大,造成查询缓慢。主要的原因是扫描行数过多。这个时候可以通过程序,分段进行查询,循环遍历,将结果合并处理进行展示。
3.12 不建议使用%前缀模糊查询
例如LIKE“%name”或者LIKE“%name%”,这种查询会导致索引失效而进行全表扫描。但是可以使用LIKE “name%”。那么如何解决这个问题呢,答案:使用全文索引或ES全文检索
3.13 避免在where子句中对字段进行表达式操作
3.14 避免隐式类型转换
where子句中出现column字段的类型和传入的参数类型不一致的时候发生的类型转换,建议先确定
where中的参数类型。 where age='18'
3.15 对于联合索引来说,要遵守最左前缀法则
举列来说索引含有字段id、name、school,可以直接用id字段,也可以id、name这样的顺序,但是
name;school都无法使用这个索引。所以在创建联合索引的时候一定要注意索引字段顺序,常用的查询
字段放在最前面。
3.16 必要时可以使用force index来强制查询走某个索引
有的时候MySQL优化器采取它认为合适的索引来检索SQL语句,但是可能它所采用的索引并不是我们想
要的。这时就可以采用forceindex来强制优化器使用我们制定的索引。
3.17 注意范围查询语句
对于联合索引来说,如果存在范围查询,比如between、>、<等条件时,会造成后面的索引字段失效。
3.18 使用JOIN优化
LEFT JOIN A表为驱动表,INNER JOIN MySQL会自动找出那个数据少的表作用驱动表,RIGHT JOIN B表为驱动表。