MySQL笔记
存储引擎
MySQL的结构:连接层 -> 服务层 -> 引擎层 -> 存储层
各存储引擎的特点:
InnoDB
支持 事务、外键、行级锁
MySQL中,数据库对应文件系统中的一个文件夹,每个InnoDB表都对应该文件夹下的一个 .ibd 表空间文件,该文件用于存储表结构、索引、数据
表空间文件存储的逻辑结构为:
-
一个表空间下存放多个段
-
一个段下存放多个区,一个区大小为1M
-
一个区下存放多个页,一页大小为16K
-
一个页下存放多个行
-
一行就表示表中一行数据,需要注意的是,除了本身就有的字段外,还额外有 事务id 和 Roll指针
MyISAM
不支持事务、外键、行级锁,支持表锁
在文件系统中以三个文件表示:.sdi(表结构) .MYD(数据) .MYI(索引)
Memory
数据存放在内存中,支持hash索引
只保存一个 .sdi 文件,用于保存表结构数据
索引
索引能提高查询速度,但会拖慢增删改的速度
索引设计原则总结:
索引结构
B+树索引:三个存储引擎都支持
Hash索引:只有Memory支持,高效率,不支持范围搜索,不支持排序
B树和B+树的区别:B+树所有数据都存放在叶子节点中,并且叶子节点形成一个单向链表。MySQL中,将单向链表修改为双向链表
为什么MySQL使用B+树做索引?
不管是用B树还是红黑树还是B+树,每个节点都需要保存在页中,而一页的大小是固定的(16K),如果在里面存放数据,则一页就存不了多少索引,而B+树只存放索引,故可以存放更多索引,层级更低,且将叶子节点连接可以用于顺序搜索
索引分类
索引类型:
-
主键索引:只能有一个
-
唯一索引:可以有多个
-
常规索引
-
全文索引
根据存储形式,又分为:
-
聚集索引:必须有且只有一个,叶子节点存放行数据。默认是主键索引,如果没有主键,则使用第一个唯一索引,否则自动生成一个rowid作为隐藏列当作聚集索引
-
二级索引:可以有多个,例如对任意列手动创建的一个普通索引。叶子节点存放主键,此时需要回表查询(根据主键再去查聚集索引)
索引操作
# 创建索引,加上 UNIQUE 表示创建唯一索引,索引可以指定多列,及为联合索引
CREATE [UNIQUE|FULLTEXT] INDEX index_name ON TABLE_NAME (col1, col2...)
# 查看索引
SHOW INDEX FROM TABLE_NAME
# 删除索引
DROP INDEX FROM TABLE_NAME
SQL性能分析方式
慢查询日志
该日志文件会记录所有执行时间超过了指定阈值(默认long_query_time=10s)的sql语句。该功能默认关闭,通过修改mysql配置文件后重启mysql即可
profile工具:
通过执行 show profiles 命令可以查看所有已执行sql的耗时情况,该功能默认关闭,通过 SET profile=1 开启。
explain命令查看执行计划:
直接在sql语句前面加上 explain 或者 desc 关键字即可,它会返回以下列:
-
id:多表查询时,表的查询顺序,如果id相同,则顺序执行,否则越大越先执行
-
select_type:查询类型
-
type:表连接类型,见下文
-
possible_key:可能用到的索引
-
key:实际用到的索引
-
key_len:索引字段最大可能长度,不是实际使用长度,越短说明效率越高
-
rows:可能查询的行数
-
filtered:返回的结果与扫描的行的占比
-
Extra:额外字段
表连接类型(效率递减):
-
NULL:查询时不访问任何表,例如 select 1
-
system:
-
const:主键或唯一索引
-
eq_ref:
-
ref:非唯一索引
-
range
-
index
-
all
索引失效
使用原则1:最左前缀法则(适用于联合索引)
查询从联合索引的最左列开始,不会跳过索引列,如果跳过中间索引列,则其后续索引列均失效,注意:最左列存在即可,与其在sql中的位置无关
例如创建了顺序为 A、B、C三列的联合索引,执行 select * from table where B=xx and A=xx and C=xx 会使用该联合索引,而 select * from table where A=xx and C=xx 只会使用索引A
使用原则2:范围查询后续的索引失效,但是如果范围中保护=,则后续索引仍然生效
例如 where A=xx and B>0 and C=xx,C索引失效,但如果修改为 where A=xx and B**>=**0 and C=xx,则C索引也会生效
使用原则3:在索引上使用运算操作将导致索引失效
例如使用内置函数对索引列计算后查询
使用原则4:对于字符串类型的列,如果没加单引号,则会进行隐式类型转换,索引失效(本质上是原则3)
使用原则5:头部模糊搜索,索引失效
使用原则6:or连接的条件,任意一侧没有索引,则两边索引都失效(很奇怪,应该至少左侧有索引的话可以使用索引啊,但实际并非如此)
使用原则7:如果mysql认为使用索引更慢,则不会使用索引,例如绝大多数数据都符合条件,则可能不使用索引
SQL提示
mysql会自动判断使不使用索引或者使用哪些索引(例如同时具有联合索引和普通列索引时用哪个),但有时并没有选择最优的,此时就可以进行人工干预,告诉mysql使用哪些索引,这个方法称为 SQL提示:
-
use index:select * from table use index(idx_xxx_xxx) where xxx=xxx; 只是建议mysql使用该索引,它可能不用
-
ignore index:告诉mysql不要用什么索引
-
force index:强制使用什么索引
覆盖索引
查询使用了索引,并且查询所需的列在索引中,例如创建了 A、B两个索引,使用 select A, B from table where A=xx and B=xx 即使用了覆盖索引,如果查询的列不止 A 和 B,还有C,则需要没有走覆盖所有
如果 explain 的 Extra 中出现了(不同版本的数据库可能不同):
-
using index condition:表示查找时使用了索引,但仍需要使用回表查询数据
-
using where; using index:使用了索引,并且查找的列在索引中都能找到,即覆盖所有
前缀索引
如果字段类型为字符串,而数据可能很长时,如果对其建立索引可能会是的索引很大,此时就可以只对其一部分前缀建立索引。前缀索引需要回表
# 对column列前n个字符建立前缀索引
create index idx_xxx on table_name(column(n))
n如何确定?
计算各个长度的前缀表示该字段唯一的概率,可以使用下述命令:
# substring(column_name, start, end) 函数用于截取start到end的子串,然后计算它唯一的个数占总行数的比值,越接近1越好,n值可以一个一个试
select count(distinct substring(col, 1, n) / count(*) from table)
其他SQL的优化
插入优化
-
批量插入:避免多次开启连接
-
手动提交事务:可以合并多次insert,避免频繁开启关闭事务带来的损耗
-
如果插入数据中有主键字段,则建议按主键顺序进行插入,详情见下文 主键优化
-
如果要插入超大的数据,则不建议使用insert,而使用load命令。插入前需要将待插入的数据以一定数据格式保存到文件,使用load命令时告诉它行和列的区分符号是什么即可
主键索引
mysql数据和索引存储在聚簇索引的那个B+树上,叶子节点存放数据,并使用双向链表进行连接,其他节点存放索引。需要注意的是,数据其实仍然是使用链表按主键(聚簇索引)顺序进行连接的,索引的目的仅仅是加快检索速度
页分裂与页合并
mysql中的数据存放在页中,而一页的大小是固定的,由于数据页是以链表形式按主键顺序进行连接的,当我要插入一个主键在链表中间范围的数据时,如果待插入的页已经满了,则会将该页中间以后的数据转移到一个新的页中,并将新插入的数据存放到新的页中,然后修改原来页前后两个页的指针,这个过程称为页分裂,故,如果按主键索引顺序向空表插入数据,则不会出现页分裂
当我要删除一些行时,mysql会将页中这些行数据进行逻辑删除,当删除后剩余数据量小于页容量的一半时,则会尝试将其两侧的页合并成一页,如果合并后大于一页,则不合并,这个过程称为页合并
主键设计原则
-
尽量降低主键长度:普通索引的叶节点保存的是主键(聚簇索引),所以主键短就能保存更多主键
-
尽量使用自增主键
-
尽量不使用uuid或其他非自然主键,插入时可能频繁涉及页分裂
-
尽量避免修改主键,因为修改主键不仅需要修改聚簇索引的数据,还需要修改普通索引的叶节点
order by 优化
索引本身就是一个有序的数据结构,order by的作用就是排序,所以在mysql中,使用order by排序会有两种情况:
-
使用索引进行排序:在 Extra 列能看到 Using index 信息
-
将查询到的数据放入排序缓冲区进行排序,在 Extra 中可以看到 Using filesort,效率低
联合索引的情况下,如果排序顺序和联合索引顺序不同,则只会部分使用索引排序,部分使用文件排序
如果排序方式均为倒序,则也会使用索引排序(如果有索引排序),如果是部分倒叙,则可能两种排序方式都用
注:创建联合索引时,可以指定索引是倒序还是顺序
create index idx_xxx on table(col1 asc, col2 desc)
只有使用了覆盖索引的前提下才会使用索引排序(待验证)
group by 优化
类似order by使用索引的情况
注:假如我创建了顺序为 A、B 的联合索引,如果此时直接 group by B,则不会使用索引,而如果使用条件 where A=xxx group by B,则会使用索引
limit 优化
没有使用索引的情况下,limit起始位置越大,耗时越长
优化方式:覆盖索引+子查询
具体操作:
例如我要查询一张大表中第10000条数据的后10行,一般做法为 select * from table limit 10000,10
此时并不会使用任何索引,它会顺序检索到第10000条数据
使用覆盖索引:select col1 from t1 order by col1 limit 10000,10;这里为col1创建了索引。由于使用了覆盖索引,此时不会回表,但此时只获取了col1列的信息,我想获取其他列的信息怎么办?
一种直观的方法是使用 in 关键字:select * from t1 where col1 in (上面的sql),但这句话实际上是执行不了的,因为 in 关键字中的sql不支持 limit 语句
解决方法是将上述查询出来的结果当作一张虚表进行连接查询:
select t1.* from t1, (上面的sql) t2 where t1.col1=t2.col1
count 优化
count是一个用于查询总数据量的聚合函数
在InnoDB中,执行count(*)时,会把数据一行一行读出来进行计数
几种形式:
-
count(*):统计总记录数,不会取值
-
count(字段):统计该字段中有多少个非NULL值
-
count(主键):同2,只不过由于主键不可能为NULL,则少了判断的步骤
-
count(1):同count(*),不会读取每条记录内容,而是判定有记录则加1
综上,2、3是需要读取字段内容的,1、4同样速度
update 优化
如果更新记录是根据索引字段进行更新的,则只会在该行记录加上行锁
但如果更新条件没有用到索引值,则会直接使用表锁,此时其他事务想要更新任意记录都必须等待表锁释放
存储对象
视图
一种由sql定义的虚拟表,本质上就是sql的查询结果
为什么要用视图?
灵活,安全(可以对视图进行单独的权限操作,对一些用户屏蔽一些字段)
创建视图:create or replace view view_name as SELECT语句
查询视图:可以直接将视图当作一张表来操作,操作方式同真实的表
删除视图:drop view view_name
可以添加或修改视图数据吗?
视图并不持有真实的数据,但确实可以对视图进行增删改操作,其操作的数据会直接修改基表(视图select的表)的数据
但是,如果视图的select语句带有一定条件,例如id<10,而我向视图里面插入了一条id>10的数据会发生什么呢?
基表中仍然会插入该数据,但视图却无法返回该数据。解决方法是在创建视图语句后面加上视图的检查选项 create view … with cascaded/local check option,它会在插入时检测插入的数据是否符合该视图的定义,如果不符合则插入失败报错,如果使用的是cascaded,则如果使用了级联视图(select的是视图),则在修改数据时,会级联检查是否符合视图条件,否则只检查第一级视图(这两者需要重新查阅资料)
对视图中的行做更新操作,则视图中的行必须在基表中有对应的数据,如果没有(例如视图中的数据是使用sum、count、group等聚合函数计算得到)则无法更新
存储过程
将多条sql封装起来复用,可以接收参数和返回数据,减少客户端与mysql的通信
存储过程的使用:
# 创建
create procedure pro_name(参数列表)
begin
...
# 注:如果在命令行创建存储过程,内部sql使用;表示结束,但是命令行遇到;会认为命令输入结束了,所以在创建存储过程前先使用命令修改命令结束符,创建完成后修改回来
end;
# 调用
call pro_name;
# 删除
drop procedure pro_name;
环境变量:mysql中有session和global两种环境变量。session的变量只会影响当前会话。
设置环境变量:set session/global autocommit=1 # 设置自动提交
注:设置了global环境变量重启失效,需修改mysql配置文件
用户自定义变量:
设置:set @my_name := ‘xxx’; 或者 select xxx into @my_name from table;
查询:select @my_name; # 如果变量没有提前声明,则为NULL,不会报错
存储过程的局部变量:
声明变量:declare my_var int default 0;
赋值变量:select xxx into my_var from table; 或者 set my_var := xxx
if 条件:
if 条件1 then
...
elseif 条件2 then
...
else
...
end if;
存储过程的参数:
参数有 in/out/inout 三种,如果为in类型,则表示调用时需要传递的参数,如果为out类型,则表示该变量会被存储过程返回,并将返回值赋值给该out类型变量,如果是 inout 类型,则该参数被传进去后又被赋予新值作为返回值,例如:
create procedure my_pro(in parm1 int, out parm2 varchar(10))
begin
...
end;
存储函数
有返回值的的存储过程,参数只能是in类型的
create function my_name(parm1 int)
returns int
begin
...
return ...;
end;
触发器
创建一段sql集合,在数据表任意行被修改前后会触发该sql集合,其中会包含两个默认参数 old 和 new 表示老的和新的数据
create trigger my_name
before/after insert/update/delete on table_name for each row
begin
...
end
锁
按颗粒度分:
-
全局锁
-
表级锁
-
行级锁
全局锁
加锁后整个数据库就处于只读状态,典型应用为做全库的备份,要保证数据的一致性
示例:加锁后备份数据然后释放锁
# 加锁
flush tables with read lock;
# 此时,该数据库只能读不能写,否则会处于阻塞状态
# 数据库快照,注,这句话不是在mysql client中执行,而是在shell中执行
mysqldump -uroot -p xxx db_name > db_name.sql
# 释放锁
unlock tables;
表级锁
表锁
表锁分为 读锁 和 写锁
# 加锁
lock tables 表名... read/write
# 解锁
unlock tables
读锁:不会阻塞其他客户端的读,但会阻塞写,本客户端也只能读
写锁:阻塞其他客户端的读写,但本客户端可以读写
元数据锁
在访问表时会自动加上,目的是维护表元数据的数据一致性,也是分为读锁和写锁。例如当修改表结构时加写锁。
注意,对表内数据的修改是不会修改表结构的,所以加的元数据锁仍然是读锁
意向锁
某事务在修改表数据时,会对该行记录加上行级锁,假如此时其他事务要对该表加上表锁,则会出现冲突
解决方法为:事务在加行级锁的同时,对整个表加上一个意向锁,该意向锁分为共享锁(IS,允许再给表加读锁)和排它锁(IX,不允许加表锁)
例如事务中执行 select … lock in share mode; 此时行就会加上共享锁,表加上IS意向共享锁
如果执行 update … 就会自动加上意向排他锁
行级锁
行锁
分为共享锁(S)和排它锁(X)
注意,行级锁是基于索引加的锁,而非行记录加锁,意味着如果不是使用索引修改指定的数据行,则会升级为表锁
间隙锁
为了避免幻读,只锁间隙
临键锁(next-key lock)
行锁 + 间隙锁
在对非唯一普通索引加锁修改时,会加上临键锁,它不仅会锁住检索到的行,同时还会锁住这些行到上下两个不同元素间的间隙,这是因为该所以并不是唯一索引,避免在该事务执行过程中,其他事务又插入了相同索引值的数据
InnoDB
逻辑存储结构
表空间中有多个段,一个段中有多个区,一个区中有多个页,一页中有多行,一行中有多列
表空间:一个表对应一个 .idb 文件,就是一个表空间文件
段:表空间的内容又分为几个段,例如索引段(B+树非叶子节点)、数据段(叶子节点)、回滚段(undo log)等
区:一个段中有多个区,默认一个区为1M,InnoDB申请磁盘空间时,为了保证空间的连续性,会以区为单位申请
架构
内存结构:
mysql对数据的操作并非直接操作磁盘,而是操作一块内存缓冲区,由后台线程根据策略将缓冲区的数据刷回磁盘
日志也是一样,只操作日志缓存区的内容,后台根据策略刷入磁盘
缓冲区会占据绝大多数mysql使用的内存
磁盘结构:
对缓冲区的数据也并非直接刷回磁盘,而是先保存到磁盘中一个叫 双写缓冲区 的区域,这样做的好处时可以用于系统异常时恢复数据
事务原理
innoDB如何保证ACID?
使用 redo log 和 undo log 保证 原子性、一致性、持久性
使用 锁机制 和 MVCC 保证 隔离性
redo log 重放
提交事务时记录的日志,实现持久性
由两部分组成 redo log buffer(内存)和 redo log file(磁盘)
innodb操作的数据页都指的是内存缓冲区中的数据,如果对这些数据做了修改,则它们和磁盘中的数据就不一致了(但事务提交仍然是返回ok),称之为脏页。此时由后台线程自动将脏页数据刷回磁盘,假如此时发生故障无法刷回,则会很麻烦。
解决方法是:当事务在提交后,修改内存缓冲区的页数据同时,将该次操作记录到redo log中,如果发生上述故障,则可以使用redo log 中的记录重放操作
redo log日志并不需要保存所有的历史数据,如果数据成功刷回磁盘,则可以删除该redo log
为什么不直接将数据刷回磁盘?
如果执行一个修改操作后客户端需要等待刷回磁盘后才返回状态,则效率非常低,而如果将该操作记录下来,如果刷回磁盘失败则可以重放这个操作,这种思想称之为 先写日志
undo log 回滚
实现原子性,会将修改的反向操作记录在undo log中,用于提供回滚和实现MVCC机制
MVCC 多版本并发控制
当前读
读取最新的数据,读取当前记录时要保证该记录不会被其他事务修改,常见的操作有:
select … lock in share mode;
select … for update、insert、delete;
上述两个sql会加锁,mysql默认隔离级别为可重复读,即一个事务内读取的数据始终是一致的,即使此时其他事务修改了这些记录,但是,如果是在同一个事务中执行上述两个命令,则可以得到最新的数据,即其他事务修改后的数据
快照读
普通的select,不会加锁。在一个事务中,第一个select得到的数据才是快照读,后续读取的都是该快照,也就是说,如果在一个事务中,在其他事务修改数据提交之前一直没有执行select该数据,则只会执行的select获取的也是最新的数据,并非事务开启前的数据
MVCC
隐藏字段
在innodb中,每个表都会隐式创建两个列 DB_TRX_ID 和 DB_ROLL_PTR,如果表没有主键,则还会隐式创建一个主键 DB_ROW_ID
-
DB_TRX_ID:最近修改该行数据的事务id
-
DB_ROLL_PTR:配合undo log 指向该记录上一个版本
注:innodb中,事务id是自增的int值
undo log 版本链
当一个事务在修改某行数据时,会先将修改的反向操作保存到undo log中,并记录一个地址指针,数据修改后,会将该记录的 DB_TRX_ID 修改为该事务的id,并将 DB_ROLL_PTR 修改为undo log中记录的那条数据的指针
如此一来,如果某条记录有多次修改,则在undo log中就会记录一条版本链,链头就是最后一次修改前的数据,链尾则是最原始的数据
另外,undolog的更新过程中也会产生自己的redolog,用于保证持久化。
readview
快照读时,读取的并非最新的数据,而是undo log版本链中的历史数据,那么应该读的是哪一条历史数据呢?这就是由readview控制的
每一条sql语句都会生产一个readview,readview会记录当前未提交的事务id,readview中包含四个核心字段:
-
m_ids:当前还未提交的事务id
-
min_trx_id:最小活跃事务id
-
max_trx_id:下一次事务会被分配的id,即当前最新事务id+1
-
creator_trx_id:readview创建者事务id
在不同的隔离级别,生成readview时机也不同:
-
在读已提交级别中,每次快照读都会生成一个readview,因为每次快照读都需要读取最新提交的修改值
-
在可重复读级别中,仅第一次快照读生成,后续直接复用
应该读取哪个版本数据?
应该是readview中最后一次提交的事务id的记录(需查资料看看详细步骤)
另外,或许有个疑问,当我在同一个事务中出现了先后两次读取数据(可重复读的隔离级别下),但两次读的范围是包含关系(例如第一次读的条件范围是 x<10,第二次读的范围是 x<20),那么此时readview到底指的是什么?是针对每次读的sql生成的一个readview吗?那么这里我第二次读的数据包含了第一次读的数据,事务id到底应该以谁为准?
其实仔细想想,就会发现,mvcc所说的事务id一直指的都是对单行数据的事务id,也就是说你的readview其实是针对单行数据来说的,上例中,第一次读取了 x < 10 的数据,那么对每一行都有一个readview,而第二次读取 x<20 又包含了新的数据,那么readview其实指的是第一步读的那些readview加上新读的数据产生的readview。此为个人理解
数据库管理
mysql默认有四个数据库:
-
mysql:mysql运行所需的各种信息,例如用户、时区、主从复制相关等
-
information_schema:数据库的一些元数据,例如存储过程、函数、数据库支持的字符集、视图、触发器等
-
performace_schema:保存了当前数据库的一些运行状态,例如当前加了哪些锁等
-
sys:主要保存一些和性能调优相关的一些视图
mysql中常用的工具:
mysqladmin:检查服务器的配置和状态,或者创建删除数据库等
mysqldump:数据库备份迁移
运维相关
日志
错误日志
# 查看日志位置
show variables like '%log_err%'
bin log
记录了DDL(创建修改表等)和DML(修改表数据等),但不包含查询语句
用于:
-
数据恢复
-
主从复制
# 查看相关配置
show variables like '%log_bin%'
日志可选格式(日志本身是以二进制保存的,以下为使用 mysqlbinlog 命令解析之后的格式,保存的什么格式就只能解析出什么格式):
-
statement:记录执行的sql语句
-
row:默认,记录修改前后的值
-
mixd:默认采用statement,特殊情况下自动切换到row
查询日志
binlog中不包含查询语句日志,而查询日志会记录所有执行的sql,不管是查询语句还是修改语句等,默认关闭
慢查询日志
记录执行时间超过了阈值(long_query_time=10),并且扫描记录大于阈值(min_examined_row_limit)的所有sql,默认关闭
主从复制
将主库的binlog传送给从库,从库进行回放以达到主从复制的目的
为什么要主从复制?
-
故障转移
-
读写分离
-
主库可以只用来响应请求,从库实现数据备份等操作,减少主库压力
原理
主数据库所有的变更(数据库本身的变更以及表数据的变更)写入binlog
从数据库有两条线程:
-
IOthread:负责从主数据库拉取binlog并写入从数据库的relay log文件中
-
SQLthread:负责通过读取relay log重放操作
搭建过程
主库配置
1. 修改主库的配置文件 /etc/my.cnf,重启数据库
# 指定主库在集群中的id,需保证唯一(包括从库)
server-id=1
# 1表示只读,0表示可读写
read-only=0
# 指定同步的数据库
binlog-do-db=db_name
# 指定不同步的数据库
binlog-ignore-db=db_name
2. 登录主库,为从库创建账户名和密码,并赋予复制权限
# 创建账户,@'%' 表示该用户可以在任意主机上访问该mysql服务器
create user 'username'@'%' identified with mysql_native_password by 'password';
# 授权主从复制
grant replication slave on *.* to 'username'@'%'
3. 查看主库当前binlog位置以及需要从哪个位置开始复制同步(在开启主从复制前,数据库以及记录了一些数据,这些数据不需要同步)
# 该命令返回一个表格,其中,
# 第一列名为file,表示从哪个binlog文件开始同步,例如 binlog.00004
# 第二列名为position,表示从该binlog的哪个位置开始同步,例如537
show master status
如果想要将主库之前的数据也同步过去,可以先将之前的数据导出一个sql脚本,在从库中执行该脚本后再开启主从复制
从库配置:
4. 修改从库配置,开启主从复制,重启数据库
server-id=2
read-only=1
5. 配置主库位置
change replication source to SOURCE_HOST='xxx',
SOURCE_USER='username',
SOURCE_PASSWORD='password',
SOURCE_LOG_FILE='第3步得到的文件名',
SOURCE_LOG_POS='第3步得到的position'
6. 开始同步
start replica;
7. 查看同步状态
show replica status
它会以表的形式返回当前的同步配置,以及是否正常同步,主要查看 Replica_IO_Running 和 Replica_SQL_Running 是否为yes(即这两个线程是否正常运行)
此时主库的操作就会同步到从库
分库分表
分库分表后,原来所有的操作都只需要连接一台mysql服务器,现在需要操作多台,大大增加了开发难度,故有一些框架可以屏蔽这些细节:
-
shardingJDBC:基于AOP原理,对程序中的sql进行拦截,需自己实现解析、改写、路由等操作
-
MyCat:数据库分库分表中间件,相对于shardingJDBC更简单,可以将其看作是一个大的虚拟数据库,直接对其进行sql操作即可
MyCat
基于java开发的一个独立软件,将mysql集群虚拟成一个大的mysql服务器,应用程序完全不用管分库分表的操作,直接将原本连接mysql的配置修改为连接mycat的配置即可
mycat中的几个核心概念:
-
schema:逻辑数据库
-
table:逻辑数据表
-
dataNode:对物理节点的抽象化,逻辑表中的数据根据一定分片规则配分到逻辑节点上
具体操作为:
先在多个物理mysql主机上创建相同的数据库,然后修改mycat的配置文件 schema.xml,该配置文件就是用于配置上述三个参数,其中,核心就是schema中的分片规则
再去server.xml配置访问的用户信息及权限,该配置文件主要用于配置mycat运行时所需的一些配置
然后启动mycat,会占用8066端口
后续连接mycat就和连接mysql一样,使用方式也是透明的,包括命令行的基本操作也是一样的。
另外还有一个非常重要的配置文件 rule.xml,用于配置分片规则
如果对数据库做了垂直拆分,sql中涉及多表查询时可能出问题,因为mycat不知道应该把sql路由到哪台物理机上执行,一个解决方法是将那些需要频繁连接查询的表设置为global 全局表,即每台机器上都有一个一样的真实表
分片
-
范围分片:1-100属于哪个机器,100-200属于哪个机器等
-
取模分片:k % n = 哪台机器
-
一致性hash:计算指定字段的hash,根据hash映射到物理机,所谓一致性hash意思是,这种hash算法不会因为增加或减少物理机而使得同一个hash值映射到了不同的机器上
-
枚举分片:根据字段内容分,例如根据性别分为两台机器,根据省份保存在不同的机器等
-
应用指定:根据某字段计算出来的分片号,例如根据身份证后两位作为分片主机号
-
。。。其他一些方法也都大差不差,都是根据某字段做运算得到一个主机号
读写分离
前提是主从复制,如果是读的操作使用从库,如果是写的操作则操作主库,但这种方式会极大增加开发难度,故可以使用mycat进行操作简化
mycat提供了两个组件 writeHost 和 readHost 用于实现上述功能
balance的取值:
-
0:不开启读写分离,sql都使用writeHost
-
1:实现了读写分离,当主从备份有多个时,全部的 writeHost 和 readHost 都参与负载均衡
-
2:所有的读写随机在 writeHost 和 readHost 上分发
-
3:实现了读写分离,writeHost只负责写不负责读
多主多从:
有多个主服务器,每个主服务器都有从服务器,而主服务器之间相互同步,以达到写的压力分摊的效果