✨架构
MySQL5.5 版本开始,默认使用InnoDB存储引擎,它擅长事务处理,具有崩溃恢复特性,在日常开发中使用非常广泛。下面是InnoDB架构图,左侧为内存结构,右侧为磁盘结构。
内存结构(4个部分)
在左侧的内存结构中,主要分为这么四大块儿: Buffer Pool、Change Buffer、Adaptive Hash Index、Log Buffer。 接下来介绍一下这四个部分
Buffer Pool
- InnoDB存储引擎基于磁盘文件存储,访问物理硬盘和在内存中进行访问,速度相差很大,为了尽可能弥补这两者之间的I/O效率的差值,就需要把经常使用的数据加载到缓冲池中,避免每次访问都进行磁盘I/O。
- 在InnoDB的缓冲池中不仅缓存了索引页和数据页,还包含了undo页、插入缓存、自适应哈希索引以及InnoDB的锁信息等等。
- 缓冲池 Buffer Pool,是主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改查操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘加载并缓存),然后再以一定频率刷新到磁盘,从而减少磁盘IO,加快处理速度。
- 缓冲池以Page页为单位,底层采用链表数据结构管理Page。根据状态,将Page分为三种类型:
- free page:空闲page,未被使用。
- clean page:被使用page,数据没有被修改过。
- dirty page:脏页,被使用page,数据被修改过,也中数据与磁盘的数据产生了不一致。
在专用服务器上,通常将多达80%的物理内存分配给缓冲池 。参数设置:show variables like 'innodb_buffer_pool_size';
- 小结:先放到缓存buffer pool当中,一定频率后,将缓存中的所有数据通过一次磁盘I/O处理,避免调用磁盘I/O
Change Buffer
- Change Buffer,更改缓冲区(针对于非唯一二级索引页),在执行DML语句时,如果这些数据Page没有在Buffer Pool中,不会直接操作磁盘,而会将数据变更存在更改缓冲区 Change Buffer中,在未来数据被读取时,再将数据合并恢复到Buffer Pool中,再将合并后的数据刷新到磁盘中。
Change Buffer的意义是什么呢?
先来看一幅图,这个是二级索引的结构图
与聚集索引不同,二级索引通常是非唯一的,并且以相对随机的顺序插入二级索引。同样,删除和更新可能会影响索引树中不相邻的二级索引页,如果每一次都操作磁盘,会造成大量的磁盘IO。有了ChangeBuffer
之后,我们可以在缓冲池中进行合并处理,减少磁盘IO。
-
当MySQL执行更新操作时,它会将更改写入Change Buffer中,并将更改标记为“脏页”。当需要将脏页写入磁盘时,MySQL会将脏页与其他脏页组合成一个连续的区域,然后将整个区域写入磁盘。
-
这是因为磁盘I/O操作的成本很高,MySQL试图将需要写入磁盘的数据缩减到最小,以提高写入磁盘的效率。
-
然而,如果更改数据影响了不相邻的二级索引页,那么这些脏页可能不会被组合到一个连续的区域中。这意味着MySQL需要执行多个磁盘I/O操作,以将这些脏页写入磁盘。这将导致额外的磁盘寻道时间和延迟,降低写入磁盘的性能。
-
-
读取数据,发现数据被更改过(根据存放在changer buffer中的数据确认),合并修改数据到buffer pool,然后再将合并后的数据返回给查询
Adaptive Hash Index
-
自适应hash索引,用于优化对Buffer Pool数据的查询。MySQL的InnoDB引擎中虽然没有直接支持hash索引,但是给我们提供了一个功能就是这个自适应hash索引。因为前面我们讲到过,hash索引在进行等值匹配时,一般性能是要高于B+树的,因为hash索引一般只需要一次IO即可,而B+树,可能需要几次匹配,所以hash索引的效率要高,但是hash索引又不适合做范围查询、模糊匹配等。
-
InnoDB存储引擎会监控对表上各索引页的查询,如果观察到在特定的条件下hash索引可以提升速度,则建立hash索引,称之为自适应hash索引。
-
**自适应哈希索引,无需人工干预,是系统根据情况自动完成。**参数: adaptive_hash_index
-
MySQL在查找数据时,会按照:Adaptive Hash Index、Change Buffer、Buffer Pool 的顺序进行查找。如果数据存在于缓存中,则可以避免磁盘I/O操作,提高查询性能。如果数据不存在于缓存中,则MySQL将从磁盘中读取数据,并将其加载到缓存中,以便以后的查询可以更快地访问该数据。
触发自适应哈希索引创建的条件:
-
数据表使用了哈希索引或者没有使用索引,且查询语句中包含等值查询条件(使用“=”或者“<=>”符号);
-
查询语句的等值查询条件中的值分布均匀,即没有出现大量重复值或者单个值占比过高的情况;
-
数据表中的数据分布相对稳定,即插入、删除操作不会对数据分布产生剧烈变化。
当以上三个条件同时满足时,MySQL会自动触发Adaptive Hash Index来优化查询性能。Adaptive Hash Index会根据查询语句中的等值查询条件,动态调整哈希表大小,从而提高查询效率。
Log Buffer
-
Log Buffer:日志缓冲区,用来保存要写入到磁盘中的log日志数据(redo log 、undo log),默认大小为 16MB,日志缓冲区的日志会定期刷新到磁盘中。如果需要更新、插入或删除许多行的事务,增加日志缓冲区的大小可以节省磁盘 I/O。
-
参数:
- innodb_log_buffer_size:缓冲区大小
- innodb_flush_log_at_trx_commit:日志刷新到磁盘时机,取值主要包含以下三个:
- 1: 日志在每次事务提交时写入并刷新到磁盘,默认值。
- 0: 每秒将日志写入并刷新到磁盘一次。
- 2: 日志在每次事务提交后写入,并每秒刷新到磁盘一次。
磁盘结构(8个部分)
System Tablespace
- 系统表空间是更改缓冲区的存储区域。如果表是在系统表空间而不是每个表文件或通用表空间中创建的,它也可能包含表和索引数据。(在MySQL5.x版本中还包含InnoDB数据字典、undolog等) 。
- 参数:innodb_data_file_path;系统表空间,默认的文件名叫 ibdata1。
- 它在MySQL启动时被加载到内存中,并在整个MySQL实例的生命周期内保持打开状态。System Tablespace的作用如下:
- 存储系统表和元数据:System Tablespace用于存储系统表和元数据,这些数据对于MySQL的正常运行至关重要。如果System Tablespace损坏或丢失,则可能导致MySQL无法启动或无法访问数据库和表。
- 支持事务管理:System Tablespace是支持MySQL事务管理的重要组成部分。在事务中,MySQL需要对系统表和元数据进行读写操作,并使用System Tablespace来存储这些更改。这确保了MySQL在执行事务期间可以保持一致性和可靠性。
- 支持数据字典和缓存:System Tablespace中的系统表存储了MySQL数据字典的元数据信息,包括表结构、索引信息、用户权限、触发器等。MySQL使用数据字典来执行查询和更新操作,并将经常使用的数据缓存到内存中,以提高性能。
File-Per-Table Tablespaces
- 如果开启了innodb_file_per_table开关 ,则每个表的文件表空间包含单个InnoDB表的数据和索引 ,并存储在文件系统上的单个数据文件中。
- 开关参数:innodb_file_per_table ,该参数默认开启。也就是说,我们每创建一个表,都会产生一个表空间文件
General Tablespaces
- 通用表空间,需要通过 CREATE TABLESPACE 语法创建通用表空间,在创建表时,可以指定该表空间。
- 创建表空间:
CREATE TABLESPACE ts_name ADD DATAFILE 'file_name' ENGINE = engine_name;
- 创建表时指定表空间:
CREATE TABLE xxx ... TABLESPACE ts_name;
- 创建表空间:
Undo Tablespaces
- 撤销表空间,MySQL实例在初始化时会自动创建两个默认的undo表空间(初始大小16M),用于存储undo log日志。
Temporary Tablespaces
- InnoDB 使用会话临时表空间和全局临时表空间。存储用户创建的临时表等数据。
Doublewrite Buffer Files
- 双写缓冲区,innoDB引擎将数据页从Buffer Pool刷新到磁盘前,先将数据页写入双写缓冲区文件中,便于系统异常时恢复数据。
Redo Log
-
**重做日志,是用来实现事务的持久性。**该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都会存到该日志中, 用于在刷新脏页到磁盘时,发生错误时, 进行数据恢复使用。
-
以循环方式写入重做日志文件,涉及两个文件:
- ib_logfile0
- ib_logfile1
后台线程
在InnoDB的后台线程中,分为4类,分别是:Master Thread 、IO Thread、Purge Thread、Page Cleaner Thread。
- Master Thread
核心后台线程,负责调度其他线程,还负责将缓冲池中的数据异步刷新到磁盘中, 保持数据的一致性,还包括脏页的刷新、合并插入缓存、undo页的回收 。 - IO Thread
在InnoDB存储引擎中大量使用了AIO(Asynchronous IO)来处理IO请求, 这样可以极大地提高数据库的性能,而IO Thread主要负责这些IO请求的回调。- 可以通过以下的这条指令,查看到 InnoDB 的状态信息,其中包含IO Thread信息:
mysql> show engine innodb status \G;
- 可以通过以下的这条指令,查看到 InnoDB 的状态信息,其中包含IO Thread信息:
线程类型 | 默认个数 | 职责 |
---|---|---|
Read thread | 4 | 负责读操作 |
Write thread | 4 | 负责写操作 |
Log thread | 1 | 负责将日志缓冲区刷新到磁盘 |
Insert buffer thread | 1 | 负责将写缓冲区内容刷新到磁盘 |
-
Purge Thread
主要用于回收事务已经提交了的undo log,在事务提交之后,undo log可能不用了,就用它来回收。 -
Page Cleaner Thread
协助 Master Thread 刷新脏页到磁盘的线程,它可以减轻 Master Thread 的工作压力,减少阻塞。
✨事务原理
事务基础
事务 是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
特性
-
原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
-
一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
-
隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
-
持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
实际上,我们研究事务的原理,就是研究MySQL的InnoDB引擎是如何保证事务的这四大特性的。
而对于这四大特性,实际上分为两个部分。 其中的原子性、一致性、持久化,实际上是由InnoDB中的两份日志来保证的,一份是redo log日志,一份是undo log日志。 而隔离性是通过数据库的锁,加上MVCC来保证的。
我们在讲解事务原理的时候,主要就是来研究一下redo log,undo log以及MVCC。
redo log
重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。
该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中, 用于在刷新脏页到磁盘,发生错误时, 进行数据恢复使用。
如果没有redolog,可能会存在什么问题的? 我们一起来分析一下。
我们知道,在InnoDB引擎中的内存结构中,主要的内存区域就是缓冲池,在缓冲池中缓存了很多的数据页。 当我们在一个事务中,执行多个增删改的操作时,InnoDB引擎会先操作缓冲池中的数据,如果缓冲区没有对应的数据,会通过后台线程将磁盘中的数据加载出来,存放在缓冲区中,然后将缓冲池中的数据修改,修改后的数据页我们称为脏页。 而脏页则会在一定的时机,通过后台线程刷新到磁盘中,从而保证缓冲区与磁盘的数据一致。 而缓冲区的脏页数据并不是实时刷新的,而是一段时间之后将缓冲区的数据刷新到磁盘中,假如刷新到磁盘的过程出错了,而提示给用户事务提交成功,而数据却没有持久化下来,这就出现问题了,没有保证事务的持久性。
那么,如何解决上述的问题呢? 在 InnoDB 中提供了一份日志 redo log,接下来我们再来分析一下,通过 redo log 如何解决这个问题。
有了redolog之后,当对缓冲区的数据进行增删改之后,
-
会首先将操作的数据页的变化,记录在redo log buffer中。
-
在事务提交时,会将redo log buffer中的数据刷新到redo log磁盘文件中。
-
过一段时间之后,如果刷新缓冲区的脏页到磁盘时,发生错误,此时就可以借助于redo log进行数据恢复,这样就保证了事务的持久性。
-
而如果脏页成功刷新到磁盘或者涉及到的数据已经落盘,此时redolog就没有作用了,就可以删除了,所以存在的两个redolog文件是循环写的。
-
Redo Log 是 MySQL 中用于保证事务持久性的重要组成部分之一,用于记录所有已经提交的事务对数据库的修改。循环写多个 Redo Log 文件是指 MySQL 在运行过程中会创建多个 Redo Log 文件,并且在这些文件中循环写入数据。
当一个事务提交时,其所做的修改会被写入到 Redo Log 中,以保证在数据库崩溃或者异常情况下能够恢复已提交的事务。为了保证 Redo Log 能够持续记录事务修改,MySQL 会不断地往 Redo Log 中写入数据,当一个 Redo Log 文件写满后,MySQL 会创建新的 Redo Log 文件,从而形成多个 Redo Log 文件。这些 Redo Log 文件之间是循环写入的,也就是当最后一个文件写满后,MySQL 会回到第一个文件重新开始写入。
使用多个 Redo Log 文件的主要原因是为了提高 MySQL 的可靠性和性能。如果只使用一个 Redo Log 文件,那么一旦这个文件损坏或者出现异常,那么数据库就无法恢复已提交的事务。而使用多个 Redo Log 文件可以增加 MySQL 发生异常时能够恢复数据的机会,并且可以分摊写入 Redo Log 的负载,提高性能。此外,MySQL 还可以在后台将多个 Redo Log 文件合并成一个文件,以减少文件数量和管理成本。
因此,循环写多个 Redo Log 文件是 MySQL 数据库中的一项基础技术,通过这种方式,MySQL 能够确保数据的持久性,并提高数据库的可靠性和性能。
-
那为什么每一次提交事务,要刷新redo log 到磁盘中呢,而不是直接将buffer pool中的脏页刷新到磁盘呢 ?
- 因为在业务操作中,我们操作数据一般都是随机读写磁盘的,而不是顺序读写磁盘。 而redo log在往磁盘文件中写入数据,由于是日志文件,所以都是顺序写的。顺序写的效率,要远大于随机写。 这种先写日志的方式,称之为 WAL(Write-Ahead Logging)。
undo log
回滚日志,用于记录数据被修改前的信息 , 作用包含两个:提供回滚(保证事务的原子性) 和MVCC(多版本并发控制) 。
undo log和redo log记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。
Undo log销毁:undo log在事务执行时产生,事务提交时,并不会立即删除undo log,因为这些日志可能还用于MVCC。
Undo log存储:undo log采用段的方式进行管理和记录,存放在前面介绍的 rollback segment回滚段中,内部包含1024个undo log segment。
✨MVCC
-
当前读
读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。对于我们日常的操作,如:select … lock in share mode(共享锁),select … for update、update、insert、delete(排他锁)都是一种当前读。
-
快照读简单的select(不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。
- Read Committed:每次select,都生成一个快照读。
- Repeatable Read:开启事务后第一个select语句才是快照读的地方。
- Serializable:快照读会退化为当前读。
在测试中,我们看到即使事务B提交了数据,事务A中也查询不到。 原因就是因为普通的select是快照读,而在当前默认的RR隔离级别下,开启事务后第一个select语句才是快照读的地方,后面执行相同的select语句都是从快照中获取数据,可能不是当前的最新数据,这样也就保证了可重复读。
MVCC全称 Multi-Version Concurrency Control,多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,快照读为MySQL实现MVCC提供了一个非阻塞读功能。MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log日志、readView。
隐藏字段
当我们某张表,我们在查看表结构的时候,就可以显式的看到自己设置的字段。 实际上除了这些字段外,InnoDB还会自动的给我们添加三个隐藏字段及其含义分别是:
隐藏字段 | 含义 |
---|---|
DB_TRX_ID | 最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID。 |
DB_ROLL_PTR | 回滚指针,指向这条记录的上一个版本,用于配合undo log,指向上一个版本。 |
DB_ROW_ID | 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。 |
前两个字段是肯定会添加的, 是否添加最后一个字段DB_ROW_ID,得看当前表有没有主键,如果有主键,则不会添加该隐藏字段。
测试
查看有主键的表 stu
进入服务器中的 /var/lib/mysql/itcast/ , 查看stu的表结构信息, 通过如下指令:ibd2sdi stu.ibd
查看没有主键的表 employee
create table employee (id int , name varchar(10));
- 因为employee表没有指定主键,所以有如下字段。
undolog
回滚日志,在insert、update、delete的时候产生的便于数据回滚的日志。
当insert的时候,产生的undo log日志只在回滚时需要,在事务提交后,可被立即删除。
而update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会立即被删除。
版本链
有一张表原始数据为:
- DB_TRX_ID : 代表最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID,是自增的。
- DB_ROLL_PTR : 由于这条数据是才插入的,没有被更新过,所以该字段值为null。
当事务4执行第一条修改语句时,也会记录undo log日志,记录数据变更之前的样子; 然后更新记录,并且记录本次操作的事务ID,回滚指针,回滚指针用来指定如果发生回滚,回滚到哪一个版本。
可知,若有四个并发事务同时在访问这张表。不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。
readview
ReadView(读视图)是 快照读 SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id。
ReadView中包含了四个核心字段:
字段 | 含义 |
---|---|
m_ids | 当前活跃的事务ID集合 |
min_trx_id | 最小活跃事务ID |
max_trx_id | 预分配事务ID,当前最大事务ID+1(因为事务ID是自增的) |
creator_trx_id | ReadView创建者的事务ID |
而在readview中就规定了版本链数据的访问规则:
- trx_id 代表当前undolog版本链对应事务ID。
条件 | 是否可以访问 | 说明 |
---|---|---|
trx_id == creator_trx_id | 可以访问该版本 | 成立,说明数据是当前这个事务更改的。 |
trx_id < min_trx_id | 可以访问该版本 | 成立,说明数据已经提交了。 |
trx_id > max_trx_id | 不可以访问该版本 | 成立,说明该事务是在ReadView生成后才开启。 |
min_trx_id <= trx_id <= max_trx_id | 如果trx_id不在m_ids中,是可以访问该版本的 | 成立,说明数据已经提交。 |
不同的隔离级别,生成ReadView的时机不同:
- READ COMMITTED :在事务中每一次执行快照读时生成ReadView。
- REPEATABLE READ:仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。
原理分析
RC隔离级别
RC隔离级别下,在事务中每一次执行快照读时生成ReadView
分析如下图事务5的快照读读取数据是如何获取数据的
-
若隔离级别为Read Committed,那么每一次进行快照读都会生成一个ReadView
-
需要根据所生成的ReadView以及ReadView的版本链访问规则,到undolog版本链中匹配数据,最终决定此次快照读返回的数据。
-
在进行匹配时,会从undo log的版本链,从上到下进行挨个匹配:
- 先匹配
id| age| name| DB_TRX_ID| DB_ROLL_PTR : 30| 3| A3| 4| 0x00003
这条记录,这条记录对应的trx_id为4,也就是将4带入右侧的匹配规则中。 ①不满足 ②不满足 ③不满足 ④也不满足 ,都不满足,则继续匹配undo log版本链的下一条。 - 再匹配第二条
上一条记录的回滚指针指向地址0x00003 id| age| name| DB_TRX_ID| DB_ROLL_PTR : 30| 3| A3| 3| 0x00002
,这条记录对应的trx_id为3,也就是将3带入右侧的匹配规则中。①不满足 ②不满足 ③不满足 ④也不满足 ,都不满足,则继续匹配undo log版本链的下一条 - 再匹配第三条
上一条记录的回滚指针指向地址0x00002 id| age| name| DB_TRX_ID| DB_ROLL_PTR : 30| 3| A3| 3| 0x00001
,这条记录对应的trx_id为2,也就是将2带入右侧的匹配规则中。①不满足 ②满足 终止匹配,此次快照读,返回的数据就是版本链中记录的这条数据。
- 先匹配
RR隔离级别
RR隔离级别下,仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。 而RR 是可重复读,在一个事务中,执行两次相同的select语句,查询到的结果是一样的。
我们看到,在RR隔离级别下,只是在事务中第一次快照读时生成ReadView,后续都是复用该ReadView,那么既然ReadView都一样, ReadView的版本链匹配规则也一样, 那么最终快照读返回的结果也是一样的。
所以呢,MVCC的实现原理就是通过 InnoDB表的隐藏字段、UndoLog 版本链、ReadView来实现的。而MVCC + 锁,则实现了事务的隔离性。 而一致性则是由redolog 与 undolog保证。
四个隔离级别(读未提交、读提交、可重复读和串行化)在使用MVCC和锁来实现隔离性时,存在相同和不同之处。
相同之处:
- MVCC机制:所有隔离级别都使用MVCC机制来实现数据的隔离性,MVCC通过为每个事务维护数据的版本号来避免读取到其他事务未提交或已回滚的数据。
- 读锁和写锁:所有隔离级别都使用读锁和写锁来保证事务之间的隔离性,读锁用于保证并发读取数据时的一致性,写锁用于保证在修改数据时其他事务无法读取或修改该数据。
不同之处:
- 隔离级别:四个隔离级别的隔离程度不同,读未提交最弱,串行化最强,可重复读和读提交则介于两者之间。因此,各级别的隔离实现方式也有所不同。
- 锁粒度:随着隔离级别的提高,锁的粒度也逐渐升高。读未提交和读提交级别的锁粒度较小,只需要使用行级锁即可,而可重复读和串行化级别的锁粒度则较大,需要使用表级锁或页面级锁。
- 锁类型:各个隔离级别使用的锁类型不同。例如,读未提交和读提交级别主要使用共享锁和排他锁,而可重复读和串行化级别则更多地使用间隙锁和Next-Key锁。
- 事务特性:各个隔离级别对事务的特性要求也不同。例如,读未提交级别不要求事务具有原子性和持久性,而串行化级别要求事务具有原子性、持久性和隔离性。
MySQL管理
系统数据库
Mysql数据库安装完成后,自带了一下四个数据库,具体作用如下:
数据库 | 含义 |
---|---|
mysql | 存储MySQL服务器正常运行所需要的各种信息 (时区、主从、用户、权限等) |
information_schema | 提供了访问数据库元数据的各种表和视图,包含数据库、表、字段类型及访问权限等 |
performance_schema | 为MySQL服务器运行时状态提供了一个底层监控功能,主要用于收集数据库服务器性能参数 |
sys | 包含了一系列方便 DBA 和开发人员利用 performance_schema性能数据库进行性能调优和诊断的视图 |
常用工具
mysql
该mysql不是指mysql服务,而是指mysql的客户端工具。
语法 :
user> mysql [options] [database]
选项 :
-u, --user=name #指定用户名
-p, --password[=name] #指定密码
-h, --host=name #指定服务器IP或域名
-P, --port=port #指定连接端口
-e, --execute=name #执行SQL语句并退出
-e
选项可以在Mysql客户端执行SQL语句,而不用连接到MySQL数据库再执行,对于一些批处理脚本,这种方式尤其方便。
mysqladmin
mysqladmin 是一个执行管理操作的客户端程序。可以用它来检查服务器的配置和当前状态、创建并删除数据库等。
-- windows环境
通过帮助文档查看选项:
user> mysqladmin --help
语法:
user> mysqladmin [options] command ...
选项:
-u, --user=name #指定用户名
-p, --password[=name] #指定密码
-h, --host=name #指定服务器IP或域名
-P, --port=port #指定连接端口
example:
user> mysqladmin -uroot –p1234 drop test01 -- 数据库名,不需要加单引号和分号
user> mysqladmin -uroot –p1234 version -- command,不需要加分号
mysqlbinlog
由于服务器生成的二进制日志文件以二进制格式保存,所以如果想要检查这些文本的文本格式,就会使用到mysqlbinlog 日志管理工具。
语法 :
user> mysqlbinlog [options] log-files1 log-files2 ...
选项 :
-d, --database=name 指定数据库名称,只列出指定的数据库相关操作。
-o, --offset=# 忽略掉日志中的前n行命令。
-r,--result-file=name 将输出的文本格式日志输出到指定文件。
-s, --short-form 显示简单格式, 省略掉一些信息。
--start-datatime=date1 --stop-datetime=date2 指定日期间隔内的所有日志。
--start-position=pos1 --stop-position=pos2 指定位置间隔内的所有日志。
补充:
MySQL的复制方式:(配置主要是通过MySQL中的全局变量binlog_format设置)
- 基于语句的复制(Statement-Based Replication,SBR):将主服务器上执行的 SQL 语句复制到从服务器上执行,是最基本的复制方式。适用于主从服务器之间的网络带宽较宽裕,对数据一致性要求不高的场景。
- 基于行的复制(Row-Based Replication,RBR):将主服务器上的数据行复制到从服务器上,适用于数据变更比较频繁的场景,可以减少网络带宽的消耗。
- 混合复制(Mixed Replication):结合 SBR 和 RBR,自动选择使用哪种方式进行复制,适用于数据变更不规律的场景。
- GTID 复制:使用全局事务标识符(Global Transaction Identifier,GTID)来跟踪主服务器和从服务器之间的复制进度,确保数据的一致性。适用于多主复制和容灾场景。
- 组复制(Group Replication):基于 Paxos 算法实现的多主复制方式,允许多个 MySQL 服务器在同一时刻作为主服务器,实现高可用和负载均衡。适用于对高可用性和可扩展性要求较高的场景。
- 基于半同步复制(Semi-Synchronous Replication):在主服务器将数据变更写入二进制日志(Binary Log)之后,等待至少一个从服务器确认接收到了数据变更后才提交事务。可以提高数据一致性,但会增加网络延迟。
#基于语句的复制(Statement-Based Replication,SBR):
在主服务器上执行以下 SQL 命令启用基于语句的复制:
mysql> SET GLOBAL binlog_format = 'STATEMENT';
在从服务器上执行以下 SQL 命令启用基于语句的复制:
mysql> CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=107;
mysql> START SLAVE;
#基于行的复制(Row-Based Replication,RBR):
#在主服务器上执行以下 SQL 命令启用基于行的复制:
mysql> SET GLOBAL binlog_format = 'ROW';
#在从服务器上执行以下 SQL 命令启用基于行的复制:
mysql> CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=107;
mysql> START SLAVE;
#混合复制(Mixed Replication):
#在主服务器上执行以下 SQL 命令启用混合复制:
mysql> SET GLOBAL binlog_format = 'MIXED';
#在从服务器上执行以下 SQL 命令启用混合复制:
mysql> CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=107;
mysql> START SLAVE;
#GTID (Global Transaction ID)是全局事务ID 复制:
#在主服务器和从服务器上分别执行以下 SQL 命令启用 GTID 复制:
mysql> SET @@GLOBAL.gtid_mode = ON;
mysql> SET @@GLOBAL.enforce_gtid_consistency = ON;
#在从服务器上执行以下 SQL 命令设置主服务器的连接信息:
mysql> CHANGE MASTER TO MASTER_AUTO_POSITION=1;
mysql> START SLAVE;
#组复制(Group Replication):
#在每个服务器上执行以下 SQL 命令启用组复制:
mysql> SET GLOBAL group_replication_bootstrap_group=ON;
mysql> START GROUP_REPLICATION;
#在加入组复制的从服务器上执行以下 SQL 命令加入组复制:
mysql> SET GLOBAL group_replication_bootstrap_group=OFF;
mysql> SET GLOBAL group_replication_single_primary_mode=OFF;
mysql> START GROUP_REPLICATION;
mysql> SELECT * FROM performance_schema.replication_group_members;
#基于半同步复制(Semi-Synchronous Replication):
#在主服务器上执行以下 SQL 命令启用基于半同步复制:
mysql> SET GLOBAL rpl_semi_sync_master_enabled = 1;
mysql> SET GLOBAL rpl_semi_sync_master_wait_for_slave_count = 1;
#在从服务器上执行以下 SQL 命令启用基于半同步复制:
mysql> SET GLOBAL rpl_semi_sync_slave_enabled = 1
mysqlshow
mysqlshow 客户端对象查找工具,用来很快地查找存在哪些数据库、数据库中的表、表中的列或者索引。
语法 :
user> mysqlshow [options] [db_name [table_name [col_name]]]
选项 :
--count 显示数据库及表的统计信息(数据库,表 均可以不指定)
-i 显示指定数据库或者指定表的状态信息
示例:
#查询test库中每个表中的字段数,及行数
mysqlshow -uroot -p2143 test --count
#查询test库中book表的详细情况
mysqlshow -uroot -p2143 test book --count
#查看数据库db01中的course表的id字段的信息
mysqlshow -uroot -p1234 db01 course id --count
mysqldump
mysqldump 客户端工具用来备份数据库或在不同数据库之间进行数据迁移。备份内容包含创建表,及插入表的SQL语句
语法 :
mysqldump [options] db_name [tables]
mysqldump [options] --database/-B db1 [db2 db3...]
mysqldump [options] --all-databases/-A
连接选项 :
-u, --user=name #指定用户名
-p, --password=<password> #指定密码
-h, --host=name #指定服务器ip或域名
-P, --port=<port> # 指定连接端口
输出选项:
--add-drop-database #在每个数据库创建语句前加上 drop database 语句
--add-drop-table #在每个表创建语句前加上 drop table 语句 , 默认开启 ; 不开启 (--skip-add-drop-table)
-n, --no-create-db #不包含数据库的创建语句
-t, --no-create-info #不包含数据表的创建语句
-d, --no-data #不包含数据
-T, --tab=name #自动生成两个文件:一个.sql文件,创建表结构的语句;一个.txt文件,数据文件
example:
#备份db01数据库
user> mysqldump -uroot -p1234 db01 > db01.sql
#备份db01数据库中的表数据,不备份表结构(-t)
user> mysqldump -uroot -p1234 -t db01 > db01.sql
#将db01数据库的表的表结构与数据分开备份(-T)
user> mysqldump -uroot -p123321 -T "C:/xxx" for_exercise score
表结构文件:score.sql
表中数据文件:score.txt
#执行上述指令,会出错,数据不能完成备份,原因是因为我们所指定的数据存放目录/root,MySQL认为是不安全的,需要存储在MySQL信任的目录下。那么,哪个目录才是MySQL信任的目录呢,可以查看一下系统变量 secure_file_priv 。
mysql> show variables like 'secure_file_priv';
备份出来的数据包含:
- 删除表的语句
- 创建表的语句
- 数据插入语句
如果我们在数据备份时,不需要创建表,或者不需要备份数据,只需要备份表结构,都可以通过对应的参数来实现。
mysqlimport/source
mysqlimport
mysqlimport 是客户端数据导入工具,用来导入mysqldump 加 -T 参数后导出的文本文件。
#语法 :
user> mysqlimport [options] db_name textfile1 [textfile2...]
#示例 :
user> mysqlimport -uroot -p123321 -P3307 for_exercise "C:/ProgramData/MySQL/MySQL Server 8.0/Uploads/xxx.txt"
source
如果需要导入sql文件,可以使用mysql中的source 指令 :
#语法 :
mysql> use for_exercise
mysql> source /root/xxxxx.sql #注意反斜杠的方向,且不用加双引号和分号
日志
错误日志
错误日志是 MySQL 中最重要的日志之一,它记录了当 mysqld 启动和停止时,以及服务器在运行过程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时,建议首先查看此日志。
该日志是默认开启的,默认存放目录 /var/log/
,默认的日志文件名为mysqld.log
。查看日志位置:
该日志是默认开启的,默认存放目录 /var/log/,默认的日志文件名为 mysqld.log
。查看日志位置:show variables like '%log_error%';
二进制日志
二进制日志(BINLOG)记录了所有的 DDL(数据定义语言)语句和 DML(数据操纵语言)语句,但不包括数据查询(SELECT、SHOW)语句。
作用:①. 灾难时的数据恢复;②. MySQL的主从复制。在MySQL8版本中,默认二进制日志是开启着的,涉及到的参数如下:show variables like '%log_bin%'
参数说明:
- log_bin_basename:当前数据库服务器的binlog日志的基础名称(前缀),具体的binlog文件名需要再该basename的基础上加上编号(编号从000001开始)。
- log_bin_index:binlog的索引文件,里面记录了当前服务器关联的binlog文件有哪些。
格式
MySQL服务器中提供了多种格式来记录二进制日志,具体格式及特点如下:
日志格式 | 含义 |
---|---|
STATEMENT | 基于SQL语句的日志记录,记录的是SQL语句,对数据进行修改的SQL都会记录在日志文件中。 |
ROW | 基于行的日志记录,记录的是每一行的数据变更。(默认) |
MIXED | 混合了STATEMENT和ROW两种格式,默认采用STATEMENT,在某些特殊情况下会自动切换为ROW进行记录。 |
查看相关变量:show variables like '%binlog_format%';
在Linux系统下,如果我们需要配置二进制日志的格式,只需要在 /etc/my.cnf 中配置 binlog_format 参数即可。
查看
由于日志是以二进制方式存储的,不能直接读取,需要通过二进制日志查询工具 mysqlbinlog 来查看,具体语法:
#mysqlbinlog [ 参数选项 ] logfilename
#参数选项:
-d 指定数据库名称,只列出指定的数据库相关操作。
-o 忽略掉日志中的前n行命令。
-v 将行事件(数据变更)重构为SQL语句
-vv 将行事件(数据变更)重构为SQL语句,并输出注释信息
删除
对于比较繁忙的业务系统,每天生成的binlog数据巨大,如果长时间不清除,将会占用大量磁盘空间。可以通过以下几种方式清理日志:
指令 | 含义 |
---|---|
reset master | 删除全部 binlog 日志,删除之后,日志编号,将从 binlog.000001重新开始 |
purge master logs to ‘binlog.*’ | 删除 * 编号之前的所有日志 |
purge master logs before ‘yyyy-mm-dd hh24:mi:ss’ | 删除日志为 “yyyy-mm-dd hh24:mi:ss” 之前产生的所有日志 |
也可以在mysql的配置文件中配置二进制日志的过期时间,设置了之后,二进制日志过期会自动删除。
show variables like '%binlog_expire_logs_seconds%'
查询日志
查询日志中记录了客户端的所有操作语句,而二进制日志不包含查询数据的SQL语句。默认情况下,查询日志是未开启的。
如果需要开启查询日志,可以修改MySQL的配置文件 /etc/my.cnf 文件,添加如下内容:
#该选项用来开启查询日志 , 可选值 : 0 或者 1 ; 0 代表关闭, 1 代表开启
general_log=1
#设置日志的文件名 , 如果没有指定, 默认的文件名为 host_name.log
general_log_file=mysql_query.log
开启了查询日志之后,在MySQL的数据存放目录,也就是 /var/lib/mysql/ 目录下就会出现mysql_query.log 文件。之后所有的客户端的增删改查操作都会记录在该日志文件之中,长时间运行后,该日志文件将会非常大。
慢查询日志
慢查询日志记录了所有执行时间超过参数 long_query_time
设置值并且扫描记录数不小于min_examined_row_limit
的所有的SQL语句的日志,默认未开启。long_query_time
默认为10 秒,最小为 0, 精度可以到微秒。
如果需要开启慢查询日志,需要在MySQL的配置文件 /etc/my.cnf
中配置如下参数:
#慢查询日志
slow_query_log=1
#执行时间参数
long_query_time=2
默认情况下,不会记录管理语句,也不会记录不使用索引进行查找的查询。可以使用log_slow_admin_statements
和更改此行为 log_queries_not_using_indexes
,如下所述。
#记录执行较慢的管理语句
log_slow_admin_statements =1
#记录执行较慢的未使用索引的语句
log_queries_not_using_indexes = 1
上述所有的参数配置完成之后,都需要重新启动MySQL服务器才可以生效。
主从复制
使用虚拟机实验,拷贝出的虚拟机与拷贝源虚拟机不能够建立主从复制关系
主从复制是指将主数据库的 DDL
和 DML
操作通过二进制日志传到从库服务器中,然后在从库上对这些日志重新执行(也叫重做),从而使得从库和主库的数据保持同步。
MySQL支持一台主库同时向多台从库进行复制, 从库同时也可以作为其他从服务器的主库,实现链状复制。
MySQL 复制的优点主要包含以下三个方面:
- 主库出现问题,可以快速切换到从库提供服务。
- 实现读写分离,降低主库的访问压力。
- 可以在从库中执行备份,以避免备份期间影响主库服务。
原理
MySQL主从复制的核心就是 二进制日志,具体的过程如下:
从上图来看,复制分成三步:
- Master 主库在事务提交时,会把数据变更记录在二进制日志文件
Binlog
中。 - 从库读取主库的二进制日志文件
Binlog
,写入到从库的中继日志 Relay Log 。 - slave重做中继日志中的事件,将改变反映它自己的数据。
搭建
#开放指定3306端口号:
user> firewall-cmd --zone=public --add-port=3306/tcp -permanent
user> firewall-cmd -reload
#或者直接关闭服务器的防火墙:
user> systemctl stop firewalld
user> systemctl disable firewalld
准备好两台服务器之后,在上述的两台服务器中分别安装好MySQL,并完成基础的初始化准备(安装、密码配置等操作)工作。 其中:
- 192.168.200.200 作为主服务器master
192.168.200.201 作为从服务器slave
主库配置
-
修改配置文件 /etc/my.cnf
#vi /etc/my.cnf log-bin=mysql-bin #[必须]启用二进制日志 #尽量不要复制这两段代码,用手动的方式输入 #mysql 服务ID,保证整个集群环境中唯一,取值范围:1 – 232-1,默认为1 server-id=1 #是否只读,1 代表只读, 0 代表读写 read-only=0 #忽略的数据, 指不需要同步的数据库 #binlog-ignore-db=mysql #指定同步的数据库 #binlog-do-db=db01
-
重启MySQL服务器
systemctl restart mysqld
-
登录mysql,创建远程连接的账号,并授予主从复制权限
#创建itcast用户,并设置密码,该用户可在任意主机连接该MySQL服务 CREATE USER 'master'@'%' IDENTIFIED WITH mysql_native_password BY 'Root@123456'; #为 'itcast'@'%' 用户分配主从复制权限 GRANT REPLICATION SLAVE ON *.* TO 'master'@'%';
-
通过指令,查看二进制日志坐标:
mysql> show master status ;
- 字段含义说明:
- file : 从哪个日志文件开始推送日志文件,注意这里的命名规则,
- position : 从哪个位置开始推送日志
binlog_ignore_db
: 指定不需要同步的数据库
- 字段含义说明:
从库配置
-
修改配置文件 /etc/my.cnf
#尽量不要复制这两段代码,用手动的方式输入 #mysql 服务ID,保证整个集群环境中唯一,取值范围:1 ~ 2^32-1,和主库不一样即可 server-id=2 #是否只读,1 代表只读, 0 代表读写 read-only=1
-
重启MySQL服务器
systemctl restart mysqld
-
登录mysql,设置主库配置
CHANGE REPLICATION SOURCE TO SOURCE_HOST='192.168.200.200', SOURCE_USER='master', SOURCE_PASSWORD='Root@123456', SOURCE_LOG_FILE='binlog.000002', SOURCE_LOG_POS=157; #上述是8.0.23中的语法。如果mysql是 8.0.23 之前的版本,执行如下SQL: CHANGE MASTER TO MASTER_HOST='192.168.200.200', MASTER_USER='master', MASTER_PASSWORD='Root@123456', MASTER_LOG_FILE='binlog.000004', MASTER_LOG_POS=663;
参数名 含义 8.0.23之前 SOURCE_HOST 主库IP地址 MASTER_HOST SOURCE_USER 连接主库的用户名 MASTER_USER SOURCE_PASSWORD 连接主库的密码 MASTER_PASSWORD SOURCE_LOG_FILE binlog日志文件名 MASTER_LOG_FILE SOURCE_LOG_POS binlog日志文件位置 MASTER_LOG_POS -
开启同步操作
start replica; #8.0.22之后 start slave; #8.0.22之前
-
查看主从同步状态
show replica status; #8.0.22之后 show slave status; #8.0.22之前
分库分表
随着互联网及移动互联网的发展,应用系统的数据量也是成指数式增长,若采用单数据库进行数据存储,存在以下性能瓶颈:
- IO瓶颈:热点数据太多,数据库缓存不足,产生大量磁盘IO,效率较低。 请求数据太多,带宽不够,网络IO瓶颈。
- CPU瓶颈:排序、分组、连接查询、聚合统计等SQL会耗费大量的CPU资源,请求数太多,CPU出现瓶颈。
为了解决上述问题,我们需要对数据库进行分库分表处理。
分库分表的中心思想都是将数据分散存储,使得单一数据库/表的数据量变小来缓解单一数据库的性能问题,从而达到提升数据库性能的目的。
拆分策略
分库分表的形式,主要是两种:垂直拆分和水平拆分。而拆分的粒度,一般又分为分库和分表,所以组成的拆分策略最终如下:
垂直拆分
- 垂直分库
垂直分库:以表为依据,根据业务将不同表拆分到不同库中。
特点:
- 每个库的表结构都不一样。
- 每个库的数据也不一样。
- 所有库的并集是全量数据。
- 垂直分表
垂直分表:以字段为依据,根据字段属性将不同字段拆分到不同表中。
特点:
- 每个表的结构都不一样。
- 每个表的数据也不一样,一般通过一列(主键/外键)关联。
- 所有表的并集是全量数据。
水平拆分
- 水平分库
水平分库:以字段为依据,按照一定策略,将一个库的数据拆分到多个库中。
特点:
- 每个库的表结构都一样。
- 每个库的数据都不一样。
- 所有库的并集是全量数据。
- 水平分表
水平分表:以字段为依据,按照一定策略,将一个表的数据拆分到多个表中。
特点:
- 每个表的表结构都一样。
- 每个表的数据都不一样。
- 所有表的并集是全量数据。
在业务系统中,为了缓解磁盘IO及CPU的性能瓶颈,到底是垂直拆分,还是水平拆分;具体是分库,还是分表,都需要根据具体的业务需求具体分析。
实现技术
- shardingJDBC:基于AOP原理,在应用程序中对本地执行的SQL进行拦截,解析、改写、路由处理。需要自行编码配置实现,只支持java语言,性能较高。
- MyCat:数据库分库分表中间件,不用调整代码即可实现分库分表,支持多种语言,性能不及前者
MyCat概述
Mycat是开源的、活跃的、基于Java语言编写的MySQL数据库中间件。可以像使用mysql一样来使用mycat,对于开发人员来说根本感觉不到mycat的存在。
开发人员只需要连接MyCat即可,而具体底层用到几台数据库,每一台数据库服务器里面存储了什么数据,都无需关心。 具体的分库分表的策略,只需要在MyCat中配置即可。
优势:
- 性能可靠稳定
- 强大的技术团队
- 体系完善
- 社区活跃
下载
下载地址:http://dl.mycat.org.cn/
安装
Mycat是采用java语言开发的开源的数据库中间件,支持Windows和Linux运行环境,下面介绍MyCat的Linux中的环境搭建。我们需要在准备好的服务器中安装如下软件。
- MySQL
- JDK
- Mycat
服务器 | 安装软件 | 说明 |
---|---|---|
192.168.200.210 | JDK、Mycat | MyCat中间件服务器 |
192.168.200.210 | MySQL | 分片服务器 |
192.168.200.213 | MySQL | 分片服务器 |
192.168.200.214 | MySQL | 分片服务器 |
具体的安装步骤: 参考资料中提供的 《MyCat安装文档》即可,里面有详细的安装及配置步骤。
目录介绍
- bin : 存放可执行文件,用于启动停止mycat
conf:存放mycat的配置文件
lib:存放mycat的项目依赖包(jar)
logs:存放mycat的日志文件
概念介绍
MyCat的整体结构中,分为两个部分:上面的逻辑结构、下面的物理结构。
在MyCat的逻辑结构主要负责逻辑库、逻辑表、分片规则、分片节点等逻辑结构的处理,而具体的数据存储还是在物理结构,也就是数据库服务器中存储的。
MyCat入门
需求
由于 tb_order 表中数据量很大,磁盘IO及容量都到达了瓶颈,现在需要对 tb_order 表进行数据分片,分为三个数据节点,每一个节点主机位于不同的服务器上, 具体的结构,参考下图:
环境准备
准备3台服务器:
- 192.168.200.210:MyCat中间件服务器,同时也是第一个分片服务器。
- 192.168.200.213:第二个分片服务器。
- 192.168.200.214:第三个分片服务器。
并且在上述3台数据库中创建数据库 db01 。
- schema.xml
在schema.xml中配置逻辑库、逻辑表、数据节点、节点主机等相关信息。具体的配置如下:
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="DB01" checkSQLschema="true" sqlMaxLimit="100">
<--!涉及到分表操作才需要配置rule-->
<table name="TB_ORDER" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
</schema>
<dataNode name="dn1" dataHost="dhost1" database="db01" />
<dataNode name="dn2" dataHost="dhost2" database="db01" />
<dataNode name="dn3" dataHost="dhost3" database="db01" />
<dataHost name="dhost1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="master" url="jdbc:mysql://192.168.56.128:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root"
password="123321" />
</dataHost>
<dataHost name="dhost2" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="master" url="jdbc:mysql://192.168.56.131:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root"
password="123321" />
</dataHost>
<dataHost name="dhost3" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="master" url="jdbc:mysql://192.168.56.132:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root"
password="123321" />
</dataHost>
</mycat:schema>
-
安装后schema.xml文件的默认配置
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100"> <!-- auto sharding by id (long) --> <table name="travelrecord" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" /> <!-- global table is auto cloned to all defined data nodes ,so can join with any table whose sharding node is in the same data node --> <table name="company" primaryKey="ID" type="global" dataNode="dn1,dn2,dn3" /> <table name="goods" primaryKey="ID" type="global" dataNode="dn1,dn2" /> <!-- random sharding using mod sharind rule --> <table name="hotnews" primaryKey="ID" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="mod-long" /> <!-- <table name="dual" primaryKey="ID" dataNode="dnx,dnoracle2" type="global" needAddLimit="false"/> <table name="worker" primaryKey="ID" dataNode="jdbc_dn1,jdbc_dn2,jdbc_dn3" rule="mod-long" /> --> <table name="employee" primaryKey="ID" dataNode="dn1,dn2" rule="sharding-by-intfile" /> <table name="customer" primaryKey="ID" dataNode="dn1,dn2" rule="sharding-by-intfile"> <childTable name="orders" primaryKey="ID" joinKey="customer_id" parentKey="id"> <childTable name="order_items" joinKey="order_id" parentKey="id" /> </childTable> <childTable name="customer_addr" primaryKey="ID" joinKey="customer_id" parentKey="id" /> </table> <!-- <table name="oc_call" primaryKey="ID" dataNode="dn1$0-743" rule="latest-month-calldate" /> --> </schema> <!-- <dataNode name="dn1$0-743" dataHost="localhost1" database="db$0-743" /> --> <dataNode name="dn1" dataHost="localhost1" database="db1" /> <dataNode name="dn2" dataHost="localhost1" database="db2" /> <dataNode name="dn3" dataHost="localhost1" database="db3" /> <!--<dataNode name="dn4" dataHost="sequoiadb1" database="SAMPLE" /> <dataNode name="jdbc_dn1" dataHost="jdbchost" database="db1" /> <dataNode name="jdbc_dn2" dataHost="jdbchost" database="db2" /> <dataNode name="jdbc_dn3" dataHost="jdbchost" database="db3" /> --> <dataHost name="localhost1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <!-- can have multi write hosts --> <writeHost host="hostM1" url="localhost:3306" user="root" password="123456"> <!-- can have multi read hosts --> <readHost host="hostS2" url="192.168.1.200:3306" user="root" password="xxx" /> </writeHost> <writeHost host="hostS1" url="localhost:3316" user="root" password="123456" /> <!-- <writeHost host="hostM2" url="localhost:3316" user="root" password="123456"/> --> </dataHost> <!-- <dataHost name="sequoiadb1" maxCon="1000" minCon="1" balance="0" dbType="sequoiadb" dbDriver="jdbc"> <heartbeat> </heartbeat> <writeHost host="hostM1" url="sequoiadb://1426587161.dbaas.sequoialab.net:11920/SAMPLE" user="jifeng" password="jifeng"></writeHost> </dataHost> <dataHost name="oracle1" maxCon="1000" minCon="1" balance="0" writeType="0" dbType="oracle" dbDriver="jdbc"> <heartbeat>select 1 from dual</heartbeat> <connectionInitSql>alter session set nls_date_format='yyyy-mm-dd hh24:mi:ss'</connectionInitSql> <writeHost host="hostM1" url="jdbc:oracle:thin:@127.0.0.1:1521:nange" user="base" password="123456" > </writeHost> </dataHost> <dataHost name="jdbchost" maxCon="1000" minCon="1" balance="0" writeType="0" dbType="mongodb" dbDriver="jdbc"> <heartbeat>select user()</heartbeat> <writeHost host="hostM" url="mongodb://192.168.0.99/test" user="admin" password="123456" ></writeHost> </dataHost> <dataHost name="sparksql" maxCon="1000" minCon="1" balance="0" dbType="spark" dbDriver="jdbc"> <heartbeat> </heartbeat> <writeHost host="hostM1" url="jdbc:hive2://feng01:10000" user="jifeng" password="jifeng"></writeHost> </dataHost> --> <!-- <dataHost name="jdbchost" maxCon="1000" minCon="10" balance="0" dbType="mysql" dbDriver="jdbc"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="jdbc:mysql://localhost:3306" user="root" password="123456"> </writeHost> </dataHost> --> </mycat:schema>
-
server.xml
需要在server.xml中配置用户名、密码,以及用户的访问权限信息,具体的配置如下:<--! ... --> <user name="root" defaultAccount="true"> <property name="password">123321</property> <property name="schemas">DB01</property> <!-- 表级 DML 权限设置 --> <!-- <privileges check="false"> <schema name="TESTDB" dml="0110" > <table name="tb01" dml="0000"></table> <table name="tb02" dml="1111"></table> </schema> </privileges> --> </user> <user name="user"> <property name="password">123321</property> <property name="schemas">DB01</property> <property name="readOnly">true</property> </user> <--! ... -->
上述的配置表示,定义了两个用户 root 和 user ,这两个用户都可以访问 DB01 这个逻辑库,访
问密码都是123321,但是root用户访问DB01逻辑库,既可以读,又可以写,但是 user用户访问
DB01逻辑库是只读的。
测试
启动
配置完毕后,先启动涉及到的3台分片服务器,然后启动MyCat服务器。切换到Mycat的安装目录,执行如下指令,启动Mycat:
#启动
[root@localhost mycat]# bin/mycat start
#停止
[root@localhost mycat]# bin/mycat stop
Mycat启动之后,占用端口号 8066。
启动完毕之后,可以查看logs目录下的启动日志,查看Mycat是否启动完成。
[root@localhost mycat]#tail -f logs/wrapper.log
测试
- 连接MyCat
通过如下指令,就可以连接并登陆MyCat
[root@localhost mycat]# mysql -h 192.168.200.210 -P 8066 -uroot -p123456
我们看到我们是通过MySQL的指令来连接的MyCat,因为MyCat在底层实际上是模拟了MySQL的协议。
痛苦的排查过程, 不需要配置主从同步,需要将各个mysql服务器在mycat配置文件中配置正确的密码,就是因为这一点,有一台没能设置正确密码,进入mycat中创建表操作失败,显示无效的数据库链接
- ERROR 2003 (HY000): Can’t connect to MySQL
-
数据测试
然后就可以在MyCat中来创建表,并往表结构中插入数据,查看数据在MySQL中的分布情况。CREATE TABLE TB_ORDER ( id BIGINT(20) NOT NULL, title VARCHAR(100) NOT NULL, PRIMARY KEY (id) ) ENGINE=INNODB DEFAULT CHARSET=UTF8; INSERT INTO TB_ORDER(id,title) VALUES(1,'goods1'); INSERT INTO TB_ORDER(id,title) VALUES(2,'goods2'); INSERT INTO TB_ORDER(id,title) VALUES(3,'goods3'); INSERT INTO TB_ORDER(id,title) VALUES(1,'goods1'); INSERT INTO TB_ORDER(id,title) VALUES(2,'goods2'); INSERT INTO TB_ORDER(id,title) VALUES(3,'goods3'); INSERT INTO TB_ORDER(id,title) VALUES(5000000,'goods5000000'); INSERT INTO TB_ORDER(id,title) VALUES(10000000,'goods10000000'); INSERT INTO TB_ORDER(id,title) VALUES(10000001,'goods10000001'); INSERT INTO TB_ORDER(id,title) VALUES(15000000,'goods15000000'); INSERT INTO TB_ORDER(id,title) VALUES(15000001,'goods15000001');
经过测试,我们发现,在往 TB_ORDER 表中插入数据时:
- 如果id的值在1-500w之间,数据将会存储在第一个分片数据库中。
- 如果id的值在500w-1000w之间,数据将会存储在第二个分片数据库中。
- 如果id的值在1000w-1500w之间,数据将会存储在第三个分片数据库中。
- 如果id的值超出1500w,在插入数据时,将会报错。
MyCat配置
schema.xml
schema.xml 作为MyCat中最重要的配置文件之一 , 涵盖了MyCat的逻辑库 、 逻辑表 、 分片规则、分片节点及数据源的配置。
主要包含以下三组标签:
- schema标签
- datanode标签
- datahost标签
schema标签
- schema 定义逻辑库
schema 标签用于定义 MyCat实例中的逻辑库 , 一个MyCat实例中, 可以有多个逻辑库 , 可以通过 schema 标签来划分不同的逻辑库。MyCat中的逻辑库的概念,等同于MySQL中的database概念, 需要操作某个逻辑库下的表时, 也需要切换逻辑库(use xxx)。
核心属性:
- name:指定自定义的逻辑库库名
- checkSQLschema:在SQL语句操作时指定了数据库名称,执行时是否自动去除;true:自动去除,false:不自动去除
sqlMaxLimit
:如果未指定limit进行查询,列表查询模式查询多少条记录
- schema 中的table定义逻辑表
table 标签定义了MyCat中逻辑库schema下的逻辑表 , 所有需要拆分的表都需要在table标签中定义 。
核心属性:
- name:定义逻辑表表名,在该逻辑库下唯一
- dataNode:定义逻辑表所属的dataNode,该属性需要与dataNode标签中name对应;多个dataNode逗号分隔
- rule:分片规则的名字,分片规则名字是在rule.xml中定义的
- primaryKey:逻辑表对应真实表的主键
- type:逻辑表的类型,目前逻辑表只有全局表和普通表,如果未配置,就是普通表;全局表,配
置为 global
datanode标签
核心属性:
- name:定义数据节点名称
- dataHost:数据库实例主机名称,引用自 dataHost 标签中name属性
- database:定义分片所属数据库
datahost标签
该标签在MyCat
逻辑库中作为底层标签存在, 直接定义了具体的数据库实例、读写分离、心跳语句。
核心属性:
- name:唯一标识,供上层标签使用
- maxCon/minCon:最大连接数/最小连接数
- balance:负载均衡策略,取值 0,1,2,3
writeType
:写操作分发方式(0:写操作转发到第一个writeHost
,第一个挂了,切换到第二个;1:写操作随机分发到配置的writeHost
)dbDriver
:数据库驱动,支持 native、jdbc
rule.xml
rule.xml
中定义所有拆分表的规则, 在使用过程中可以灵活的使用分片算法, 或者对同一个分片算法使用不同的参数, 它让分片过程可配置化。主要包含两类标签:tableRule
、Function。
server.xml
server.xml配置文件包含了MyCat的系统配置信息,主要有两个重要的标签:system、user。
- system标签
主要配置MyCat中的系统配置信息,对应的系统配置项及其含义,如下
属性 | 取值 | 含义 |
---|---|---|
charset | utf8 | 设置Mycat的字符集, 字符集需要与MySQL的字符集保持一致 |
nonePasswordLogin | 0,1 | 0为需要密码登陆、1为不需要密码登陆,默认为0,设置为1则需要指定默认账户 |
useHandshakeV10 | 0,1 | 使用该选项主要的目的是为了能够兼容高版本的jdbc驱动, 是否采用HandshakeV10Packet来与client进行通信, 1:是, 0:否 |
useSqlStat | 0,1 | 开启SQL实时统计, 1 为开启 , 0 为关闭 ;开启之后, |
useGlobleTableCheck | 0,1 | 是否开启全局表的一致性检测。1为开启 ,0为关闭 。 |
sqlExecuteTimeout | 1000 | SQL语句执行的超时时间 , 单位为 s ; |
sequnceHandlerType | 0,1,2 | 用来指定Mycat全局序列类型,0 为本地文件,1 为数据库方式,2 为时间戳列方式,默认使用本地文件方式,文件方式主要用于测试 |
sequnceHandlerPattern | 正则表达式 | 必须带有MYCATSEQ或者 mycatseq进入序列匹配流程 注意MYCATSEQ_有空格的情况 |
subqueryRelationshipCheck | true,false | 子查询中存在关联查询的情况下,检查关联字段中是否有分片字段 .默认 false |
useCompression | 0,1 | 开启mysql压缩协议 , 0 : 关闭, 1 : 开启 |
fakeMySQLVersion | 5.5,5.6 | 设置模拟的MySQL版本号 |
defaultSqlParser | 由于MyCat的最初版本使用了FoundationDB的SQL解析器, 在MyCat1.3后增加了Druid解析器, 所以要设置defaultSqlParser属性来指定默认的解析器; 解析器有两个 :druidparser 和 fdbparser, 在MyCat1.4之后,默认是druidparser,fdbparser已经废除了 | |
processors | 1,2… | 指定系统可用的线程数量, 默认值为CPU核心 x 每个核心运行线程数量; processors 会影响processorBufferPool,processorBufferLocalPercent,processorExecutor属性, 所有, 在性能调优时, 可以适当地修改processors值 |
processorBufferChunk | 指定每次分配Socket Direct Buffer默认值为4096字节, 也会影响BufferPool长度,如果一次性获取字节过多而导致buffer不够用, 则会出现警告, 可以调大该值 | |
processorExecutor | 指定NIOProcessor上共享businessExecutor固定线程池的大小;MyCat把异步任务交给 businessExecutor线程池中, 在新版本的MyCat中这个连接池使用频次不高, 可以适当地把该值调小 | |
packetHeaderSize | 指定MySQL协议中的报文头长度, 默认4个字节 | |
maxPacketSize | 指定MySQL协议可以携带的数据最大大小, 默认值为16M | |
idleTimeout | 30 | 指定连接的空闲时间的超时长度;如果超时,将关闭资源并回收, 默认30分钟 |
txIsolation | 1,2,3,4 | 初始化前端连接的事务隔离级别,默认为REPEATED_READ , 对应数字为3 READ_UNCOMMITED=1;READ_COMMITTED=2; REPEATED_READ=3;SERIALIZABLE=4; |
sqlExecuteTimeout | 300 | 执行SQL的超时时间, 如果SQL语句执行超时,将关闭连接; 默认300秒; |
serverPort | 8066 | 定义MyCat的使用端口, 默认8066 |
managerPort | 9066 | 定义MyCat的管理端口, 默认9066 |
- user标签配置MyCat中的用户、访问密码,以及用户针对于逻辑库、逻辑表的权限信息,具体的权限描述方式及配置说明如下:
在测试权限操作时,我们只需要将 privileges 标签的注释放开。 在 privileges 下的schema标签中配置的dml属性配置的是逻辑库的权限。 在privileges的schema下的table标签的dml属性中配置逻辑表的权限。- 就近原则:逻辑表如果没有配置权限,则以逻辑库的为准
MyCat分片
垂直拆分
场景
在业务系统中, 涉及以下表结构 ,但是由于用户与订单每天都会产生大量的数据, 单台服务器的数据存储及处理能力是有限的, 可以对数据库表进行拆分, 原有的数据库表如下。
现在考虑将其进行垂直分库操作,将商品相关的表拆分到一个数据库服务器,订单表拆分的一个数据库
服务器,用户及省市区表拆分到一个服务器。最终结构如下:
准备
准备三台服务器,IP地址如图所示:
并且在192.168.200.210,192.168.200.213, 192.168.200.214上面创建数据库shopping。
配置
-
schema.xml
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="SHOPPING" checkSQLschema="true" sqlMaxLimit="100"> <table name="tb_goods_base" dataNode="dn1" primaryKey="id" /> <table name="tb_goods_brand" dataNode="dn1" primaryKey="id" /> <table name="tb_goods_cat" dataNode="dn1" primaryKey="id" /> <table name="tb_goods_desc" dataNode="dn1" primaryKey="goods_id" /> <table name="tb_goods_item" dataNode="dn1" primaryKey="id" /> <table name="tb_order_item" dataNode="dn2" primaryKey="id" /> <table name="tb_order_master" dataNode="dn2" primaryKey="order_id" /> <table name="tb_order_pay_log" dataNode="dn2" primaryKey="out_trade_no" /> <table name="tb_user" dataNode="dn3" primaryKey="id" /> <table name="tb_user_address" dataNode="dn3" primaryKey="id" /> <table name="tb_areas_provinces" dataNode="dn3" primaryKey="id"/> <table name="tb_areas_city" dataNode="dn3" primaryKey="id"/> <table name="tb_areas_region" dataNode="dn3" primaryKey="id"/> </schema> <dataNode name="dn1" dataHost="dhost1" database="shopping" /> <dataNode name="dn2" dataHost="dhost2" database="shopping" /> <dataNode name="dn3" dataHost="dhost3" database="shopping" /> <dataHost name="dhost1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="master" url="jdbc:mysql://192.168.56.128:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="123321" /> </dataHost> <dataHost name="dhost2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="master" url="jdbc:mysql://192.168.56.131:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="123321" /> </dataHost> <dataHost name="dhost3" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="master" url="jdbc:mysql://192.168.56.132:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="123321" /> </dataHost> </mycat:schema>
-
server.xml
<user name="root" defaultAccount="true"> <property name="password">123321</property> <property name="schemas">SHOPPING</property> <!-- 表级 DML 权限设置 --> <!-- <privileges check="false"> <schema name="TESTDB" dml="0110" > <table name="tb01" dml="0000"></table> <table name="tb02" dml="1111"></table> </schema> </privileges> --> </user> <user name="user"> <property name="password">123321</property> <property name="schemas">SHOPPING</property> <property name="readOnly">true</property> </user>
测试
-
上传测试SQL脚本到服务器的/root目录
-
执行指令导入测试数据重新启动MyCat后,在mycat的命令行中,通过source指令导入表结构,以及对应的数据,查看数据分布情况。
mysql> source /root/shopping-table.sql mysql> source /root/shopping-insert.sql
将表结构及对应的测试数据导入之后,可以检查一下各个数据库服务器中的表结构分布情况。 检查是否和我们准备工作中规划的服务器一致。
-
查询用户的收件人及收件人地址信息(包含省、市、区)。
在MyCat的命令行中,当我们执行以下多表联查的SQL语句时,可以正常查询出数据。select ua.user_id, ua.contact, p.province, c.city, r.area , ua.address from tb_user_address ua ,tb_areas_city c , tb_areas_provinces p ,tb_areas_region r where ua.province_id = p.provinceid and ua.city_id = c.cityid and ua.town_id = r.areaid ;
-
查询每一笔订单及订单的收件地址信息(包含省、市、区)。
实现该需求对应的SQL语句如下:SELECT order_id , payment ,receiver, province , city , area FROM tb_order_master o, tb_areas_provinces p , tb_areas_city c , tb_areas_region r WHERE o.receiver_province = p.provinceid AND o.receiver_city = c.cityid AND o.receiver_region = r.areaid ;
-
但是现在存在一个问题,订单相关的表结构是在 192.168.200.213 数据库服务器中,而省市区的数
据库表是在 192.168.200.214 数据库服务器中。
执行失败原因:MyCat在执行该SQL语句时,需要往具体的数据库服务器中路由,而当前没有一个数据库服务器完全包含了订单以及省市区的表结构,造成SQL语句失败,报错。
-
全局表
对于省、市、区/县表tb_areas_provinces , tb_areas_city , tb_areas_region,是属于数据字典表,在多个业务模块中都可能会遇到,可以将其设置为全局表,利于业务操作。
- 数据字典表:数据不多,并且数据基本不会变
修改schema.xml中的逻辑表的配置,修改 tb_areas_provinces、tb_areas_city、tb_areas_region 三个逻辑表,增加 type 属性,配置为global,就代表该表是全局表,就会在所涉及到的dataNode中创建给表。对于当前配置来说,也就意味着所有的节点中都有该表了。
<table name="tb_areas_provinces" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/>
<table name="tb_areas_city" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/>
<table name="tb_areas_region" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/>
配置完毕后,重新启动MyCat。
-
删除原来每一个数据库服务器中的所有表结构
-
通过source指令,导入表及数据
mysql> source /root/shopping-table.sql mysql> source /root/shopping-insert.sql
-
检查每一个数据库服务器中的表及数据分布,看到三个节点中都有这三张全局表
-
然后再次执行上面的多表联查的SQL语句
SELECT order_id , payment ,receiver, province , city , area FROM tb_order_master o, tb_areas_provinces p , tb_areas_city c , tb_areas_region r WHERE o.receiver_province = p.provinceid AND o.receiver_city = c.cityid AND o.receiver_region = r.areaid ;
成功正常执行。
-
当在MyCat中更新全局表的时候,我们可以看到,所有分片节点中的数据都发生了变化,每个节点的全局表数据时刻保持一致。
补充:
-
在 MyCAT 中,如果没有为分片表定义分片规则,MyCAT 会使用默认的分片规则,将数据平均分配到所有可用的分片节点上。这种默认的分片规则称为“广播规则”。
使用广播规则时,MyCAT 会将分片表的数据复制到所有分片节点上,这意味着所有分片节点上都有相同的数据。因此,广播规则适用于数据量较小、分片节点数较少的场景。
需要注意的是,广播规则可能会带来一些性能问题和数据一致性的问题。因为每个分片节点上都有相同的数据,当对分片表进行写操作时,需要将数据同步到所有分片节点上,这可能会增加网络负载和延迟,并可能导致数据一致性问题。因此,在实际应用中需要谨慎选择分片规则。
水平拆分
场景
在业务系统中, 有一张表(日志表), 业务系统每天都会产生大量的日志数据 , 单台服务器的数据存储及处理能力是有限的, 可以对数据库表进行拆分。
准备
准备三台服务器,具体的结构如下:
并且,在三台数据库服务器中分表创建一个数据库itcast。
配置
-
schema.xml
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="SHOPPING" checkSQLschema="true" sqlMaxLimit="100"> <table name="tb_goods_base" dataNode="dn1" primaryKey="id" /> <table name="tb_goods_brand" dataNode="dn1" primaryKey="id" /> <table name="tb_goods_cat" dataNode="dn1" primaryKey="id" /> <table name="tb_goods_desc" dataNode="dn1" primaryKey="goods_id" /> <table name="tb_goods_item" dataNode="dn1" primaryKey="id" /> <table name="tb_order_item" dataNode="dn2" primaryKey="id" /> <table name="tb_order_master" dataNode="dn2" primaryKey="order_id" /> <table name="tb_order_pay_log" dataNode="dn2" primaryKey="out_trade_no" /> <table name="tb_user" dataNode="dn3" primaryKey="id" /> <table name="tb_user_address" dataNode="dn3" primaryKey="id" /> <table name="tb_areas_provinces" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/> <table name="tb_areas_city" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/> <table name="tb_areas_region" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/> </schema> <schema name="ITCAST" checkSQLschema="true" sqlMaxLimit="100"> <table name="tb_log" dataNode="dn4,dn5,dn6" primaryKey="id" rule="mod-long"/> </schema> <dataNode name="dn1" dataHost="dhost1" database="shopping" /> <dataNode name="dn2" dataHost="dhost2" database="shopping" /> <dataNode name="dn3" dataHost="dhost3" database="shopping" /> <dataNode name="dn4" dataHost="dhost1" database="itcast" /> <dataNode name="dn5" dataHost="dhost2" database="itcast" /> <dataNode name="dn6" dataHost="dhost3" database="itcast" /> <dataHost name="dhost1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="master" url="jdbc:mysql://192.168.56.128:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="123321" /> </dataHost> <dataHost name="dhost2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="master" url="jdbc:mysql://192.168.56.131:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="123321" /> </dataHost> <dataHost name="dhost3" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="master" url="jdbc:mysql://192.168.56.132:3306?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8" user="root" password="123321" /> </dataHost> </mycat:schema>
tb_log表最终落在3个节点中,分别是 dn4、dn5、dn6 ,而具体的数据分别存储在 dhost1、dhost2、dhost3的itcast数据库中。
-
server.xml
配置root用户既可以访问 SHOPPING 逻辑库,又可以访问ITCAST逻辑库。<user name="root" defaultAccount="true"> <property name="password">123321</property> <property name="schemas">SHOPPING,ITCAST</property> <!-- 表级 DML 权限设置 --> <!-- <privileges check="false"> <schema name="TESTDB" dml="0110" > <table name="tb01" dml="0000"></table> <table name="tb02" dml="1111"></table> </schema> </privileges> --> </user> <user name="user"> <property name="password">123321</property> <property name="schemas">SHOPPING</property> <property name="readOnly">true</property> </user>
测试
配置完毕后,重新启动MyCat,然后在mycat的命令行中,执行如下SQL创建表、并插入数据,查看数据分布情况。
CREATE TABLE tb_log (
id bigint(20) NOT NULL COMMENT 'ID',
model_name varchar(200) DEFAULT NULL COMMENT '模块名',
model_value varchar(200) DEFAULT NULL COMMENT '模块值',
return_value varchar(200) DEFAULT NULL COMMENT '返回值',
return_class varchar(200) DEFAULT NULL COMMENT '返回值类型',
operate_user varchar(20) DEFAULT NULL COMMENT '操作用户',
operate_time varchar(20) DEFAULT NULL COMMENT '操作时间',
param_and_value varchar(500) DEFAULT NULL COMMENT '请求参数名及参数值',
operate_class varchar(200) DEFAULT NULL COMMENT '操作类',
operate_method varchar(200) DEFAULT NULL COMMENT '操作方法',
cost_time bigint(20) DEFAULT NULL COMMENT '执行方法耗时, 单位 ms',
source int(1) DEFAULT NULL COMMENT '来源 : 1 PC , 2 Android , 3 IOS',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO tb_log (id, model_name, model_value, return_value, return_class, operate_user, operate_time, param_and_value, operate_class, operate_method, cost_time,source) VALUES('1','user','insert','success','java.lang.String','10001','2022-01-06 18:12:28','{\"age\":\"20\",\"name\":\"Tom\",\"gender\":\"1\"}','cn.itcast.controller.UserController','insert','10',1);
INSERT INTO tb_log (id, model_name, model_value, return_value, return_class, operate_user, operate_time, param_and_value, operate_class, operate_method, cost_time,source) VALUES('2','user','insert','success','java.lang.String','10001','2022-01-06 18:12:27','{\"age\":\"20\",\"name\":\"Tom\",\"gender\":\"1\"}','cn.itcast.controller.UserController','insert','23',1);
INSERT INTO tb_log (id, model_name, model_value, return_value, return_class, operate_user, operate_time, param_and_value, operate_class, operate_method, cost_time,source) VALUES('3','user','update','success','java.lang.String','10001','2022-01-06 18:16:45','{\"age\":\"20\",\"name\":\"Tom\",\"gender\":\"1\"}','cn.itcast.controller.UserController','update','34',1);
INSERT INTO tb_log (id, model_name, model_value, return_value, return_class, operate_user, operate_time, param_and_value, operate_class, operate_method, cost_time,source) VALUES('4','user','update','success','java.lang.String','10001','2022-01-06 18:16:45','{\"age\":\"20\",\"name\":\"Tom\",\"gender\":\"1\"}','cn.itcast.controller.UserController','update','13',2);
INSERT INTO tb_log (id, model_name, model_value, return_value, return_class, operate_user, operate_time, param_and_value, operate_class, operate_method, cost_time,source) VALUES('5','user','insert','success','java.lang.String','10001','2022-01-06 18:30:31','{\"age\":\"200\",\"name\":\"TomCat\",\"gender\":\"0\"}','cn.itcast.controller.UserController','insert','29',3);
INSERT INTO tb_log (id, model_name, model_value, return_value, return_class, operate_user, operate_time, param_and_value, operate_class, operate_method, cost_time,source) VALUES('6','user','find','success','java.lang.String','10001','2022-01-06 18:30:31','{\"age\":\"200\",\"name\":\"TomCat\",\"gender\":\"0\"}','cn.itcast.controller.UserController','find','29',2);
分片规则
范围分片
-
介绍
根据指定的字段(如本例中使用的是表中的id字段,一般在 rule.xml 文件 对应的算法中指定)及其配置的范围与数据节点的对应情况, 来决定该数据属于哪一个分片。
-
配置
schema.xml逻辑表配置:<table name="tb_operate" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
schema.xml数据节点配置:
<dataNode name="dn1" dataHost="dhost1" database="db01" /> <dataNode name="dn2" dataHost="dhost2" database="db01" /> <dataNode name="dn3" dataHost="dhost3" database="db01" />
rule.xml分片规则配置:
<tableRule name="auto-sharding-long"> <rule> <columns>id</columns> <algorithm>rang-long</algorithm> </rule> </tableRule> <function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong"> <property name="mapFile">autopartition-long.txt</property> <property name="defaultNode">0</property> </function>
分片规则配置属性含义:
属性 描述 columns 标识将要分片的表字段 algorithm 指定分片函数与function的对应关系 class 指定该分片算法对应的类 mapFile 对应的外部配置文件 type 默认值为0 ; 0 表示Integer , 1 表示String defaultNode 默认节点 默认节点的所用:枚举分片时,如果碰到不识别的枚举值, 就让它路由到默认节点 ; 如果没有默认值,碰到不识别的则报错 。 在rule.xml中配置分片规则时,关联了一个映射配置文件 autopartition-long.txt,该配置文件的配置如下:
# range start-end ,data node index # K=1000,M=10000. 0-500M=0 500M-1000M=1 1000M-1500M=2
含义:0-500万之间的值,存储在0号数据节点(数据节点的索引从0开始) ; 500万-1000万之间的数据存储在1号数据节点 ; 1000万-1500万的数据节点存储在2号节点 ;该分片规则,主要是针对于数字类型的字段适用。 在MyCat的入门程序中,我们使用的就是该分片规则。
-
测试
配置完毕后,重新启动MyCat,然后在mycat的命令行中,执行如下SQL创建表、并插入数据,查看数
据分布情况。CREATE TABLE tb_operate ( id bigint(20) NOT NULL COMMENT 'ID', operateuser varchar(200) DEFAULT NULL COMMENT '操作用户', operation int(2) DEFAULT NULL COMMENT '1: insert, 2: delete, 3: update , 4: select', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; insert into tb_operate (id,operateuser ,operation) values(1,'Tom',1); insert into tb_operate (id,operateuser ,operation) values(2,'Cat',2); insert into tb_operate (id,operateuser ,operation) values(3,'Rose',3); insert into tb_operate (id,operateuser ,operation) values(4,'Coco',2); insert into tb_operate (id,operateuser ,operation) values(5,'Lily',1);
取模分片
-
介绍
根据指定的字段值与节点数量进行求模运算,根据运算结果, 来决定该数据属于哪一个分片。
-
配置
schema.xml逻辑表配置:<table name="tb_log" dataNode="dn4,dn5,dn6" primaryKey="id" rule="mod-long" />
schema.xml数据节点配置:
<dataNode name="dn4" dataHost="dhost1" database="itcast" /> <dataNode name="dn5" dataHost="dhost2" database="itcast" /> <dataNode name="dn6" dataHost="dhost3" database="itcast" />
rule.xml分片规则配置:
<tableRule name="mod-long"> <rule> <columns>id</columns> <algorithm>mod-long</algorithm> </rule> </tableRule> <function name="mod-long" class="io.mycat.route.function.PartitionByMod"> <!-- how many data nodes --> <property name="count">3</property> </function>
分片规则属性说明如下:
属性 描述 columns 标识将要分片的表字段 algorithm 指定分片函数与function的对应关系 class 指定该分片算法对应的类 count 数据节点的数量 该分片规则,主要是针对于数字类型的字段适用。 在前面水平拆分的演示中,我们选择的就是取模分片。
补充:模运算,大模小,结果为大除于小的余数;小模大,直接返回小值
-
测试配置完毕后,重新启动MyCat,然后在mycat的命令行中,执行如下SQL创建表、并插入数据,查看数据分布情况。
一致性hash分片
表的主键是一个uuid,则无法用取模或者范围分片;同一个uuid,使用相同的hash函数结果总是相同的hash值
-
介绍
所谓一致性哈希,相同的哈希因子计算值总是被划分到相同的分区表中,不会因为分区节点的增加而改变原来数据的分区位置,有效的解决了分布式数据的拓容问题。
-
配置
schema.xml中逻辑表配置:<!-- 一致性hash --> <table name="tb_order" dataNode="dn4,dn5,dn6" rule="sharding-by-murmur" />
schema.xml中数据节点配置:
<dataNode name="dn4" dataHost="dhost1" database="itcast" /> <dataNode name="dn5" dataHost="dhost2" database="itcast" /> <dataNode name="dn6" dataHost="dhost3" database="itcast" />
rule.xml中分片规则配置:
<tableRule name="sharding-by-murmur"> <rule> <columns>id</columns> <algorithm>murmur</algorithm> </rule> </tableRule> <function name="murmur" class="io.mycat.route.function.PartitionByMurmurHash"> <property name="seed">0</property><!-- 默认是0 --> <property name="count">3</property> <property name="virtualBucketTimes">160</property> </function>
分片规则属性含义:
属性 描述 columns 标识将要分片的表字段 algorithm 指定分片函数与function的对应关系 class 指定该分片算法对应的类 seed 创建murmur_hash对象的种子,默认0 count 要分片的数据库节点数量,必须指定,否则没法分片 virtualBucketTimes 一个实际的数据库节点被映射为这么多虚拟节点,默认是160倍,也就是虚拟节点数是物理节点数的160倍;virtualBucketTimes*count就是虚拟结点数量 ; weightMapFile 节点的权重,没有指定权重的节点默认是1。以properties文件的格式填写,以从0开始到count-1的整数值也就是节点索引为key,以节点权重值为值。所有权重值必须是正整数,否则以1代替 bucketMapPath 用于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的murmur hash值与物理节点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何东西 -
测试
配置完毕后,重新启动MyCat,然后在mycat的命令行中,执行如下SQL创建表、并插入数据,查看数
据分布情况。
create table tb_order(
id varchar(100) not null primary key,
money int null,
content varchar(200) null
);
INSERT INTO tb_order (id, money, content) VALUES ('b92fdaaf-6fc4-11ec-b831-482ae33c4a2d', 10, 'b92fdaf8-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b93482b6-6fc4-11ec-b831-482ae33c4a2d', 20, 'b93482d5-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b937e246-6fc4-11ec-b831-482ae33c4a2d', 50, 'b937e25d-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b93be2dd-6fc4-11ec-b831-482ae33c4a2d', 100, 'b93be2f9-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b93f2d68-6fc4-11ec-b831-482ae33c4a2d', 130, 'b93f2d7d-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b9451b98-6fc4-11ec-b831-482ae33c4a2d', 30, 'b9451bcc-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b9488ec1-6fc4-11ec-b831-482ae33c4a2d', 560, 'b9488edb-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b94be6e6-6fc4-11ec-b831-482ae33c4a2d', 10, 'b94be6ff-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b94ee10d-6fc4-11ec-b831-482ae33c4a2d', 123, 'b94ee12c-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b952492a-6fc4-11ec-b831-482ae33c4a2d', 145, 'b9524945-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b95553ac-6fc4-11ec-b831-482ae33c4a2d', 543, 'b95553c8-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b9581cdd-6fc4-11ec-b831-482ae33c4a2d', 17, 'b9581cfa-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b95afc0f-6fc4-11ec-b831-482ae33c4a2d', 18, 'b95afc2a-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b95daa99-6fc4-11ec-b831-482ae33c4a2d', 134, 'b95daab2-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b9667e3c-6fc4-11ec-b831-482ae33c4a2d', 156, 'b9667e60-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b96ab489-6fc4-11ec-b831-482ae33c4a2d', 175, 'b96ab4a5-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b96e2942-6fc4-11ec-b831-482ae33c4a2d', 180, 'b96e295b-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b97092ec-6fc4-11ec-b831-482ae33c4a2d', 123, 'b9709306-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b973727a-6fc4-11ec-b831-482ae33c4a2d', 230, 'b9737293-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b978840f-6fc4-11ec-b831-482ae33c4a2d', 560, 'b978843c-6fc4-11ec-b831-482ae33c4a2d');
… TO BE CONTINUE