MySQL知识总结

⼀、 MySQL 索引篇
索引介绍
索引是什么
官⽅介绍索引是帮助 MySQL ⾼效获取数据 数据结构 。更通俗的说,数据库索引好⽐是⼀本书前
⾯的⽬录,能 加快数据库的查询速度
⽅便查找 --- 检索
索引查询内容 --- 覆盖索引
排序
⼀般来说索引本身也很⼤,不可能全部存储在内存中,因此 索引往往是存储在磁盘上的⽂件中的
(可能存储在单独的索引⽂件中,也可能和数据⼀起存储在数据⽂件中)。
我们通常所说的索引,包括聚集索引、覆盖索引、组合索引、前缀索引、唯⼀索引等,没有特别说
明,默认都是使⽤ B+ 树结构组织(多路搜索树,并不⼀定是⼆叉的)的索引。
索引的优势和劣势
优势:
可以提⾼数据检索的效率,降低数据库的 IO 成本 ,类似于书的⽬录。 -- 检索 通过 索引列对数据进⾏排序 ,降低数据排序的成本,降低了 CPU 的消耗。 -- 排序
被索引的列会⾃动进⾏排序,包括【单列索引】和【组合索引】,只是组合索引的排序要复
杂⼀些。
如果按照索引列的顺序进⾏排序,对应 order by 语句来说,效率就会提⾼很多。
where 索引列 在存储引擎层 处理
覆盖索引,不需要回表查询
劣势:
索引会占据磁盘空间
索引虽然会提⾼查询效率,但是会降低更新表的效率 。⽐如每次对表进⾏增删改操作, MySQL
仅要保存数据,还有保存或者更新对应的索引⽂件。
索引的分类
单列索引
普通索引: MySQL 中基本索引类型,没有什么限制,允许在定义索引的列中插⼊重复值和空值,
纯粹为了查询数据更快⼀点。 add index
唯⼀索引:索引列中的值必须是唯⼀的,但是允许为空值 . add unique index
主键索引:是⼀种特殊的唯⼀索引,不允许有空值。 pk
组合索引
在表中的 多个字段组合上创建的索引 add index(col1,col2..)
组合索引的使⽤,需要遵循 最左前缀原则(最左匹配原则,后⾯⾼级篇讲解)
⼀般情况下, 建议使⽤组合索引代替单列索引 (主键索引除外,具体原因后⾯知识点讲解)。
全⽂索引
只有在 MyISAM 引擎、 InnoDB 5.6 以后)上才能使⽤,⽽且只能在 CHAR,VARCHAR,TEXT 类型字段上
使⽤全⽂索引。 fulltext
优先级最⾼ 先执⾏ 不会执⾏其他索引
存储引擎 决定执⾏⼀个索引
空间索引
不做介绍,⼀般使⽤不到。
索引的使⽤
创建索引
单列索引之普通索引 单列索引之唯⼀索引
单列索引之全⽂索引
组合索引
删除索引
查看索引
索引原理分析
索引的存储结构
索引存储结构
索引是在 存储引擎中实现 的,也就是说不同的存储引擎,会使⽤不同的索引
MyISAM InnoDB 存储引擎 :只⽀持 B+ TREE 索引 , 也就是说 默认使⽤ BTREE ,不能够更换
MEMORY/HEAP 存储引擎:⽀持 HASH BTREE 索引
B 树和 B+
数据结构示例⽹站:
https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
B 树图示
CREATE INDEX index_name ON table ( column (length)) ;
ALTER TABLE table_name ADD INDEX index_name ( column (length)) ;
CREATE UNIQUE INDEX index_name ON table ( column (length)) ;
alter table table_name add unique index index_name( column );
CREATE FULLTEXT INDEX index_name ON table ( column (length)) ;
alter table table_name add fulltext index_name( column )
ALTER TABLE article ADD INDEX index_titme_time (title( 50 ), time ( 10 )) ;
DROP INDEX index_name ON table
SHOW INDEX FROM table_name \G B 树是为了磁盘或其它存储设备⽽设计的⼀种多叉(下⾯你会看到,相对于⼆叉, B 树每个内结点有多个
分⽀,即多叉)平衡查找树。 多叉平衡
B 树的⾼度⼀般都是在 2-4 这个⾼度,树的⾼度直接影响 IO 读写的次数。
如果是三层树结构 --- ⽀撑的数据可以达到 20G ,如果是四层树结构 --- ⽀撑的数据可以达到⼏⼗ T
B 树和 B+ 树的区别
B 树和 B+ 树的最⼤区别在于 ⾮叶⼦节点是否存储数据 的问题。
⾮聚集索引( MyISAM
B+ 树叶⼦节点只会存储数据⾏(数据⽂件)的指针,简单来说 数据和索引不在⼀起 ,就是⾮聚集
索引。
⾮聚集索引包含主键索引和辅助索引都会存储指针的值
主键索引
- B 树是⾮叶⼦节点和叶⼦节点都会存储数据。
- B+ 树只有叶⼦节点才会存储数据,⽽且存储的数据都是在⼀⾏上,⽽且这些数据都是有指针指向的,也
就是有顺序的。 索引列 order by 这⾥设表⼀共有三列 , 假设我们以 Col1 为主键 , 则上图是⼀个 MyISAM 表的主索引 (Primary key) 示意。
可以看出 MyISAM 的索引⽂件仅仅保存数据记录的地址。
辅助索引(次要索引)
MyISAM , 主索引和辅助索引 (Secondary key) 在结构上没有任何区别 , 只是主索引要求 key 是唯⼀的 ,
⽽辅助索引的 key 可以重复。如果我们在 Col2 上建⽴⼀个辅助索引 , 则此索引的结构如下图所示 同样也是⼀颗 B+Tree,data 域保存数据记录的地址。 因此 ,MyISAM 中索引检索的算法为⾸先按照
B+Tree 搜索算法搜索索引 , 如果指定的 Key 存在 , 则取出其 data 域的值 , 然后以 data 域的值为地址 , 读取
相应数据记录。
聚集索引( InnoDB
主键索引(聚集索引)的叶⼦节点会存储数据⾏, 也就是说数据和索引是在⼀起,这就是聚集索
引。
辅助索引只会存储主键值
如果没有没有主键,则使⽤唯⼀索引建⽴聚集索引;如果没有唯⼀索引, MySQL 会按照⼀定规则
创建聚集索引。
主键索引
1.InnoDB 要求表必须有主键 (MyISAM 可以没有 ), 如果没有显式指定 , MySQL 系统会⾃动选择⼀个可以
唯⼀标识数据记录的列作为主键 , 如果不存在这种列 , MySQL ⾃动为 InnoDB 表⽣成⼀个隐含字段作为
主键 , 类型为⻓整形。 上图是 InnoDB 主索引 ( 同时也是数据⽂件 ) 的示意图 , 可以看到叶节点包含了完整的数据记录。这种索引
叫做聚集索引。因为 InnoDB 的数据⽂件本身要按主键聚集 ,
辅助索引(次要索引)
2. 第⼆个与 MyISAM 索引的不同是 InnoDB 的辅助索引 data 域存储相应记录主键的值⽽不是地址。换
句话说 ,InnoDB 的所有辅助索引都引⽤主键作为 data 域。
聚集索引这种实现⽅式使得按主键的搜索⼗分⾼效 , 但是辅助索引搜索需要检索两遍索引 : ⾸先检索辅助
索引获得主键 , 然后⽤主键到主索引中检索获得记录。
select * from user where name='Alice' 回表查询 检索两次 ⾮主键索引 --- pk--- 索引 ---> 数据
select id,name from user where name='Alice' 不需要回表 在辅助索引树上就可以查询到了 覆盖索引
( 多⽤组合索引 ) 引申 : 为什么不建议使⽤过⻓的字段作为主键 ?
因为所有辅助索引都引⽤主索引 , 过⻓的主索引会令辅助索引变得过⼤。
同时 , 请尽量在 InnoDB 上采⽤⾃增字段做表的主键。
MyISAM InnoDB 的存储结构图示
为了更形象说明这两种索引的区别 , 我们假想⼀个表如下图存储了 4 ⾏数据。 其中 Id 作为主索引 ,
Name 作为辅助索引。 图示清晰的显示了聚簇索引和⾮聚簇索引的差异: 课堂主题
Mysql 组合索引、索引失效分析、表级锁介绍
课堂⽬标 课堂⽬标
掌握索引使⽤场景
理解组合索引的结构和掌握使⽤原则
使⽤ explain 查看 sql 执⾏计划
掌握 select_type type extra 等参数意义
理解索引失效⼝诀
编写使⽤索引的 sql
掌握 MySQL 的锁的分类
理解表级锁和元数据锁
知识要点
哪些情况需要创建索引
1. 主键⾃动建⽴唯⼀索引
2. 频繁作为查询条件的字段应该创建索引
3. 多表关联查询中,关联字段应该创建索引 on 两边都要创建索引
4. 查询中排序的字段,应该创建索引
5. 频繁查找字段 覆盖索引
6. 查询中统计或者分组字段,应该创建索引 group by
哪些情况不需要创建索引
1. 表记录太少
2. 经常进⾏增删改操作的表
3. 频繁更新的字段
4. where 条件⾥使⽤频率不⾼的字段
为什么使⽤组合索引
mysql 创建组合索引的规则是⾸先会对组合索引的最左边的,也就是第⼀个 name 字段的数据进⾏排
序,在第⼀个字段的排序基础上,然后再对后⾯第⼆个的 cid 字段进⾏排序。其实就相当于实现了类似
order by name cid 这样⼀种排序规则。
为了节省 mysql 索引存储空间以及提升搜索性能 ,可建⽴组合索引( 能使⽤组合索引就不使⽤单列索
例如:
创建组合索引(相当于建⽴了 col1,col1 col2,col1 col2 col3 三个索引):
ALTER TABLE 'table_name' ADD INDEX index_name( 'col1' , 'col2' , 'col3' ⼀颗索引树上创建 3 个索引 : 省空间
三颗索引树上分别创建 1 个索引
更容易实现覆盖索引
使⽤ 遵循最左前缀原则
1 、前缀索引 like a%
2 、从左向右匹配直到遇到范围查询 > < between like
建⽴组合索引 (a,b,c,d)
where a=1 and b=1 and c>3 and d=1
c>3 停⽌了 所以 d ⽤不到索引了
怎么办?
a,b,d,c
案例
索引失效
查看执⾏计划
介绍
MySQL 提供了⼀个 EXPLAIN 命令 , 它可以 SELECT 语句的执⾏计划进⾏分析 , 并输出 SELECT 执⾏的
详细信息 , 以供开发⼈员针对性优化 .
使⽤ explain 这个命令来查看⼀个这些 SQL 语句的执⾏计划,查看该 SQL 语句有没有使⽤上了索引,有没
有做全表扫描,这都可以通过 explain 命令来查看。
可以通过 explain 命令深⼊了解 MySQL 的基于开销的优化器,还可以获得很多可能被优化器考虑到的访
问策略的细节,以及当运⾏ SQL 语句时哪种策略预计会被优化器采⽤。
EXPLAIN 命令⽤法⼗分简单 , SELECT 语句前加上 explain 就可以了 , 例如 :
mysql> create table t1 (id int primary key ,a int ,b int,c int,d int);
mysql> alter table t1 add index idx_com(a,b,c,d);
mysql> drop index idx_com on t1;
mysql> create index idx_com on t1(a,b,d,c); 参数说明
expain 出来的信息有 10 列,分别是
案例表
id select_type table type possible_keys key key_len ref rows Extra
-- ⽤户表
create table tuser(
id int primary key,
loginname varchar ( 100 ),
name varchar ( 100 ),
age int ,
sex char ( 1 ),
dep int ,
address varchar ( 100 )
);
-- 部⻔表
create table tdep(
id int primary key,
name varchar ( 100 )
);
-- 地址表
create table taddr(
id int primary key,
addr varchar ( 100 )
);
-- 创建普通索引
mysql> alter table tuser add index idx_dep(dep);
-- 创建唯⼀索引
mysql> alter table tuser add unique index idx_loginname(loginname);
-- 创建组合索引
mysql> alter table tuser add index idx_name_age_sex(name,age,sex);
-- 创建全⽂索引
mysql> alter table taddr add fulltext ft_addr(addr); id
每个 SELECT 语句都会⾃动分配的⼀个唯⼀标识符 .
表示查询中操作表的顺序,有三种情况:
id 相同:执⾏顺序由上到下
id 不同:如果是⼦查询, id 号会⾃增, id 越⼤,优先级越⾼
id 相同的不同的同时存在
id 列为 null 的就表示这是⼀个结果集,不需要使⽤它来进⾏查询。
select_type (重要)
查询类型 ,主要⽤于区别 普通查询、联合查询 (union union all) 、⼦查询等复杂查询
simple
表示不需要 union 操作或者不包含⼦查询的简单 select 查询。有连接查询时,外层的查询为 simple ,且
只有⼀个
primary
⼀个需要 union 操作或者含有⼦查询的 select ,位于最外层的单位查询的 select_type 即为 primary 。且只
有⼀个
mysql> explain select * from tuser;
+----+-------------+-------+------+---------------+------+---------+------+---
---+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref |
rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+---
---+-------+
| 1 | SIMPLE | tuser | ALL | NULL | NULL | NULL | NULL |
1 | NULL |
+----+-------------+-------+------+---------------+------+---------+------+---
---+-------+ subquery
除了 from 字句中包含的⼦查询外,其他地⽅出现的⼦查询都可能是 subquery
dependent subquery
dependent union 类似,表示这个 subquery 的查询要受到外部表查询的影响
mysql> explain select ( select name from tuser) from tuser ;
+----+-------------+-------+-------+---------------+------------------+-------
--+------+------+-------------+
| id | select_type | table | type | possible_keys | key |
key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+------------------+-------
--+------+------+-------------+
| 1 | PRIMARY | tuser | index | NULL | idx_dep | 5
| NULL | 1 | Using index |
| 2 | SUBQUERY | tuser | index | NULL | idx_name_age_sex | 312
| NULL | 1 | Using index |
+----+-------------+-------+-------+---------------+------------------+-------
--+------+------+-------------+
mysql> explain select * from tuser where id = (select max(id) from tuser);
+----+-------------+---
----+-------+---------------+---------+---------+-------+------+--------------
----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref
| rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+------
-+------+------------------------------+
| 1 | PRIMARY | tuser | const | PRIMARY | PRIMARY | 4 | const
| 1 | NULL |
| 2 | SUBQUERY | NULL | NULL | NULL | NULL | NULL | NULL
| NULL | Select tables optimized away |
+----+-------------+-------+-------+---------------+---------+---------+------
-+------+------------------------------+ union
union 连接的两个 select 查询,第⼀个查询是 PRIMARY ,除了第⼀个表外,第⼆个以后的表 select_type
都是 union
dependent union
union ⼀样,出现在 union union all 语句中,但是这个查询要受到外部查询的影响
mysql> explain select id,name,( select name from tdep a where a .id =b .dep ) from
tuser b;
+----+--------------------+-------+--------+---------------+---------+--------
-+-------------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len
| ref | rows | Extra |
+----+--------------------+-------+--------+---------------+---------+--------
-+-------------+------+-------+
| 1 | PRIMARY | b | ALL | NULL | NULL | NULL
| NULL | 2 | NULL |
| 2 | DEPENDENT SUBQUERY | a | eq_ref | PRIMARY | PRIMARY | 4
| demo1 .b.dep | 1 | NULL |
+----+--------------------+-------+--------+---------------+---------+--------
-+-------------+------+-------+
mysql> explain select * from tuser where sex= '1' union select * from tuser
where sex= '2' ;
+----+--------------+------------+------+---------------+------+---------+----
--+------+-----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref
| rows | Extra |
+----+--------------+------------+------+---------------+------+---------+----
--+------+-----------------+
| 1 | PRIMARY | tuser | ALL | NULL | NULL | NULL |
NULL | 2 | Using where |
| 2 | UNION | tuser | ALL | NULL | NULL | NULL |
NULL | 2 | Using where |
| NULL | UNION RESULT | <union1, 2 > | ALL | NULL | NULL | NULL |
NULL | NULL | Using temporary |
+----+--------------+------------+------+---------------+------+---------+----
--+------+-----------------+ union result
包含 union 的结果集,在 union union all 语句中 , 因为它不需要参与查询,所以 id 字段为 null
derived
from 字句中出现的⼦查询,也叫做派⽣表,其他数据库中可能叫做内联视图或嵌套 select
table
显示的查询表名,如果查询使⽤了别名,那么这⾥显示的是别名
如果不涉及对数据表的操作,那么这显示为 null
如果显示为尖括号括起来的就表示这个是临时表,后边的 N 就是执⾏计划中的 id ,表示结果来⾃于
这个查询产⽣。
mysql> explain select * from tuser where sex in ( select sex from tuser where
sex= '1' union select sex from tuser where sex= '2' );
+----+--------------------+------------+-------+---------------+--------------
----+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key
| key_len | ref | rows | Extra |
+----+--------------------+------------+-------+---------------+--------------
----+---------+------+------+--------------------------+
| 1 | PRIMARY | tuser | ALL | NULL | NULL
| NULL | NULL | 2 | Using where |
| 2 | DEPENDENT SUBQUERY | tuser | index | NULL |
idx_name_age_sex | 312 | NULL | 2 | Using where ; Using index |
| 3 | DEPENDENT UNION | tuser | index | NULL |
idx_name_age_sex | 312 | NULL | 2 | Using where ; Using index |
| NULL | UNION RESULT | <union2, 3 > | ALL | NULL | NULL
| NULL | NULL | NULL | Using temporary |
+----+--------------------+------------+-------+---------------+--------------
----+---------+------+------+--------------------------+
mysql> explain select * from ( select * from tuser where sex= '1' ) b;
+----+-------------+------------+------+---------------+------+---------+-----
-+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref
| rows | Extra |
+----+-------------+------------+------+---------------+------+---------+-----
-+------+-------------+
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL
| 2 | NULL |
| 2 | DERIVED | tuser | ALL | NULL | NULL | NULL | NULL
| 2 | Using where |
+----+-------------+------------+------+---------------+------+---------+-----
-+------+-------------+ 如果是尖括号括起来的 <union M,N> ,与类似,也是⼀个临时表,表示这个结果来⾃于 union 查询
id M,N 的结果集。
type (重要)
依次从好到差:
除了 all 之外,其他的 type 都可以使⽤到索引,除了 index_merge 之外,其他的 type 只可以⽤到⼀个索
注意事项:
system
表中只有⼀⾏数据或者是空表。
const (重要)
使⽤ 唯⼀索引或者主键 ,返回记录⼀定是 1 ⾏记录的等值 where 条件时,通常 type const 。其他数据库
也叫做唯⼀索引扫描
system const eq_ref ref fulltext ref_or_null unique_subquery
index_subquery range index_merge index ALL
最少要索引使⽤到 range 级别。
mysql> explain select * from ( select * from tuser where id= 1 ) a;
+----+-------------+------------+--------+---------------+---------+---------
+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len |
ref | rows | Extra |
+----+-------------+------------+--------+---------------+---------+---------
+-------+------+-------+
| 1 | PRIMARY | <derived2> | system | NULL | NULL | NULL |
NULL | 1 | NULL |
| 2 | DERIVED | tuser | const | PRIMARY | PRIMARY | 4 |
const | 1 | NULL |
+----+-------------+------------+--------+---------------+---------+---------
+-------+------+-------+ eq_ref (重要)
关键字 : 连接字段 主键或者唯⼀性索引
此类型通常出现在多表的 join 查询 , 表示对于前表的每⼀个结果 , 都只能匹配到后表的⼀⾏结果 . 并且查
询的⽐较操作通常是 '=', 查询效率较⾼ .
mysql> explain select * from tuser where id= 1 ;
+----+-------------+-------+-------+---------------+---------+---------+------
-+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref
| rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+------
-+------+-------+
| 1 | SIMPLE | tuser | const | PRIMARY | PRIMARY | 4 | const
| 1 | NULL |
+----+-------------+-------+-------+---------------+---------+---------+------
-+------+-------+
mysql> explain select * from tuser where loginname = 'zy' ;
+----+-------------+-------+-------+---------------+---------------+---------
+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len |
ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------------+---------
+-------+------+-------+
| 1 | SIMPLE | tuser | const | idx_loginname | idx_loginname | 303 |
const | 1 | NULL |
+----+-------------+-------+-------+---------------+---------------+---------
+-------+------+-------+
mysql> explain select a .id from tuser a left join tdep b on a .dep =b .id ;
+----+-------------+-------+--------+---------------+---------+---------+-----
--------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref
| rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+-----
--------+------+-------------+
| 1 | SIMPLE | a | index | NULL | idx_dep | 5 | NULL
| 2 | Using index |
| 1 | SIMPLE | b | eq_ref | PRIMARY | PRIMARY | 4 |
demo1 .a.dep | 1 | Using index |
+----+-------------+-------+--------+---------------+---------+---------+-----
--------+------+-------------+ ref (重要)
针对⾮唯⼀性索引 ,使⽤ 等值( = )查询 ⾮主键。或者是使⽤了 最左前缀规则索引的查询
fulltext
-- ⾮唯⼀索引
mysql> explain select * from tuser where dep= 1 ;
+----+-------------+-------+------+---------------+---------+---------+-------
+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref
| rows | Extra |
+----+-------------+-------+------+---------------+---------+---------+-------
+------+-------+
| 1 | SIMPLE | tuser | ref | idx_dep | idx_dep | 5 | const
| 1 | NULL |
+----+-------------+-------+------+---------------+---------+---------+-------
+------+-------+
-- 等值⾮主键连接
mysql> explain select a .id from tuser a left join tdep b on a .name =b .name ;
+----+-------------+-------+-------+---------------+------------------+-------
--+--------------+------+--------------------------+
| id | select_type | table | type | possible_keys | key |
key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+------------------+-------
--+--------------+------+--------------------------+
| 1 | SIMPLE | a | index | NULL | idx_name_age_sex | 312
| NULL | 2 | Using index |
| 1 | SIMPLE | b | ref | ind_name | ind_name | 72
| demo1 .a.name | 1 | Using where ; Using index |
+----+-------------+-------+-------+---------------+------------------+-------
--+--------------+------+--------------------------+
-- 最左前缀
mysql> explain select * from tuser where name = 'zhaoyun' ;
+----+-------------+-------+------+------------------+------------------+-----
----+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key |
key_len | ref | rows | Extra |
+----+-------------+-------+------+------------------+------------------+-----
----+-------+------+-----------------------+
| 1 | SIMPLE | tuser | ref | idx_name_age_sex | idx_name_age_sex | 303
| const | 1 | Using index condition |
+----+-------------+-------+------+------------------+------------------+-----
----+-------+------+-----------------------+
思考: explain select * from tuser where sex = '1' ; 全⽂索引检索,要注意,全⽂索引的优先级很⾼,若全⽂索引和普通索引同时存在时, mysql 不管代
价,优先选择使⽤全⽂索引
ref_or_null
ref ⽅法类似,只是增加了 null 值的⽐较。实际⽤的不多。
unique_subquery
⽤于 where 中的 in 形式⼦查询,⼦查询返回不重复值唯⼀值
index_subquery
⽤于 in 形式⼦查询使⽤到了辅助索引或者 in 常数列表,⼦查询可能返回重复值,可以使⽤索引将⼦查询
去重。
range (重要)
索引范围扫描 ,常⻅于使⽤ >,<,is null,between ,in ,like 等运算符的查询中。
mysql> explain select * from taddr where match(addr) against( 'bei' );
+----+-------------+-------+----------+---------------+---------+---------+---
---+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len |
ref | rows | Extra |
+----+-------------+-------+----------+---------------+---------+---------+---
---+------+-------------+
| 1 | SIMPLE | tuser | fulltext | ft_addr | ft_addr | 0 |
NULL | 1 | Using where |
+----+-------------+-------+----------+---------------+---------+---------+---
---+------+-------------+
mysql> explain select id from tuser where id> 1 ;
+----+-------------+-------+-------+---------------+---------+---------+------
+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref
| rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+------
+------+-------------+
| 1 | SIMPLE | tuser | range | PRIMARY | PRIMARY | 4 | NULL
| 1 | Using where |
+----+-------------+-------+-------+---------------+---------+---------+------
+------+-------------+
--like 前缀索引
mysql> explain select * from tuser where name like 'zhao%' ;
+----+-------------+-------+-------+------------------+------------------+----
-----+------+------+-----------------------+
| id | select_type | table | type | possible_keys | key |
key_len | ref | rows | Extra | index_merge
表示查询使⽤了两个以上的索引,最后取交集或者并集,常⻅ and or 的条件使⽤了不同的索引,官⽅
排序这个在 ref_or_null 之后,但是实际上由于要读取所个索引,性能可能⼤部分时间都不如 range
index (重要)
关键字:条件是出现在索引树中的节点的。可能没有完全匹配索引。
索引全表扫描 ,把索引从头到尾扫⼀遍,常⻅于使⽤索引列就可以处理不需要读取数据⽂件的查询、可
以使⽤索引排序或者分组的查询。
+----+-------------+-------+-------+------------------+------------------+----
-----+------+------+-----------------------+
| 1 | SIMPLE | tuser | range | idx_name_age_sex | idx_name_age_sex | 303
| NULL | 1 | Using index condition |
+----+-------------+-------+-------+------------------+------------------+----
-----+------+------+-----------------------+
注: like '%z' 不使⽤索引
-- 单索引
mysql> explain select loginname from tuser;
+----+-------------+-------+-------+---------------+---------------+---------
+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len |
ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------------+---------
+------+------+-------------+
| 1 | SIMPLE | tuser | index | NULL | idx_loginname | 303 |
NULL | 2 | Using index |
+----+-------------+-------+-------+---------------+---------------+---------
+------+------+-------------+
-- 组合索引
mysql> explain select age from tuser;
+----+-------------+-------+-------+---------------+------------------+-------
--+------+------+-------------+
| id | select_type | table | type | possible_keys | key |
key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+------------------+-------
--+------+------+-------------+
| 1 | SIMPLE | tuser | index | NULL | idx_name_age_sex | 312
| NULL | 2 | Using index |
+----+-------------+-------+-------+---------------+------------------+-------
--+------+------+-------------+
思考: explain select loginname,age from tuser; all (重要)
这个就是全表扫描数据⽂件,然后再 server 层进⾏过滤 返回符合要求的记录。
possible_keys
此次查询中可能选⽤的索引,⼀个或多个
key
查询真正使⽤到的索引, select_type index_merge 时,这⾥可能出现两个以上的索引,其他的
select_type 这⾥只会出现⼀个。
key_len
⽤于处理查询的索引⻓度,如果是单列索引,那就整个索引⻓度算进去,如果是多列索引,那么查
询不⼀定都能使⽤到所有的列,具体使⽤到了多少个列的索引,这⾥就会计算进去,没有使⽤到的
列,这⾥不会计算进去。
留意下这个列的值,算⼀下你的多列索引总⻓度就知道有没有使⽤到所有的列了。
另外, key_len 只计算 where 条件⽤到的索引⻓度,⽽排序和分组就算⽤到了索引,也不会计算到
key_len 中。
ref
如果是使⽤的常数等值查询,这⾥会显示 const
如果是连接查询,被驱动表的执⾏计划这⾥会显示驱动表的关联字段
如果是条件使⽤了表达式或者函数,或者条件列发⽣了内部隐式转换,这⾥可能显示为 func
rows
覆盖索引
mysql> explain select * from tuser;
+----+-------------+-------+------+---------------+------+---------+------+---
---+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref |
rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+---
---+-------+
| 1 | SIMPLE | tuser | ALL | NULL | NULL | NULL | NULL |
2 | NULL |
+----+-------------+-------+------+---------------+------+---------+------+---
---+-------+
回表查询 这⾥是执⾏计划中估算的扫描⾏数,不是精确值( InnoDB 不是精确的值, MyISAM 是精确的值,主要原
因是 InnoDB ⾥⾯使⽤了 MVCC 并发机制)
extra (重要)
这个列包含不适合在其他列中显示单⼗分重要的额外的信息,这个列可以显示的信息⾮常多,有⼏⼗
种,常⽤的有
using temporary
表示使⽤了临时表存储中间结果。
MySQL 在对查询结果 order by group by 时使⽤临时表
临时表可以是内存临时表和磁盘临时表,执⾏计划中看不出来,需要查看 status 变量,
used_tmp_table used_tmp_disk_table 才能看出来。
no tables used
不带 from 字句的查询或者 From dual 查询
使⽤ not in() 形式⼦查询或 not exists 运算符的连接查询,这种叫做反连接
即,⼀般连接查询是先查询内表,再查询外表,反连接就是先查询外表,再查询内表。
using filesort (重要)
排序时⽆法使⽤到索引时,就会出现这个。常⻅于 order by group by 语句中
mysql> explain select distinct a.id from tuser a,tdep b where a.dep=b.id;
+----+-------------+-------+--------+-------------------------------------
-----------+---------+---------+------------+------+----------------------
---------------------+
| id | select_type | table | type | possible_keys
| key | key_len | ref | rows | Extra
|
+----+-------------+-------+--------+-------------------------------------
-----------+---------+---------+------------+------+----------------------
---------------------+
| 1 | SIMPLE | a | index |
PRIMARY,idx_loginname,idx_name_age_sex,idx_dep | idx_dep | 5 | NULL
| 2 | Using where; Using index; Using temporary |
| 1 | SIMPLE | b | eq_ref | PRIMARY
| PRIMARY | 4 | kkb2.a.dep | 1 | Using index; Distinct
|
+----+-------------+-------+--------+-------------------------------------
-----------+---------+---------+------------+------+----------------------
---------------------+ 说明 MySQL 会使⽤⼀个外部的索引排序,⽽不是按照索引顺序进⾏读取。
MySQL 中⽆法利⽤索引完成的排序操作称为 ⽂件排序
using index (重要)
查询时 不需要回表查询 ,直接通过索引就可以获取查询的数据。
表示相应的 SELECT 查询中使⽤到了 覆盖索引( Covering Index ,避免访问表的数据⾏,效率不
错!
如果同时出现 Using Where ,说明索引被⽤来执⾏查找索引键值
如果没有同时出现 Using Where ,表明索引⽤来读取数据⽽⾮执⾏查找动作。
using where (重要)
表示存储引擎返回的记录并不是所有的都满⾜查询条件,需要在 server 层进⾏过滤。
mysql> explain select * from tuser order by address;
+----+-------------+-------+------+---------------+------+---------+------+---
---+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref |
rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+---
---+----------------+
| 1 | SIMPLE | tuser | ALL | NULL | NULL | NULL | NULL |
2 | Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+---
---+----------------+
mysql> explain select name,age,sex from tuser ;
+----+-------------+-------+-------+---------------+------------------+-------
--+------+------+-------------+
| id | select_type | table | type | possible_keys | key |
key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+------------------+-------
--+------+------+-------------+
| 1 | SIMPLE | tuser | index | NULL | idx_name_age_sex | 312
| NULL | 2 | Using index |
+----+-------------+-------+-------+---------------+------------------+-------
--+------+------+-------------+
全值匹配 覆盖索引
-- 查询条件⽆索引
mysql> explain select * from tuser where address= 'beijing' ; +----+-------------+-------+------+---------------+------+---------+------+---
---+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref |
rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+---
---+-------------+
| 1 | SIMPLE | tuser | ALL | NULL | NULL | NULL | NULL |
2 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+---
---+-------------+
-- 索引失效
mysql> explain select * from tuser where age= 1 ;
+----+-------------+-------+------+---------------+------+---------+------+---
---+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref |
rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+---
---+-------------+
| 1 | SIMPLE | tuser | ALL | NULL | NULL | NULL | NULL |
2 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+---
---+-------------+
mysql> explain select * from tuser where id in ( 1 , 2 );
+----+-------------+-------+-------+---------------+---------+---------+------
+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref
| rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+------
+------+-------------+
| 1 | SIMPLE | tuser | range | PRIMARY | PRIMARY | 4 | NULL
| 2 | Using where |
+----+-------------+-------+-------+---------------+---------+---------+------
+------+-------------+
查询条件中分为限制条件和检查条件, 5.6 之前,存储引擎只能根据限制条件扫描数据并返回,然
server 层根据检查条件进⾏过滤再返回真正符合查询的数据。 5.6.x 之后⽀持 ICP 特性,可以把检
查条件也下推到存储引擎层,不符合检查条件和限制条件的数据,直接不读取,这样就⼤⼤减少了
存储引擎扫描的记录数量。 extra 列显示 using index condition firstmatch(tb_name)
5.6.x 开始引⼊的优化⼦查询的新特性之⼀,常⻅于 where 字句含有 in() 类型的⼦查询。如果内表的数据
量⽐较⼤,就可能出现这个
loosescan(m..n)
5.6.x 之后引⼊的优化⼦查询的新特性之⼀,在 in() 类型的⼦查询中,⼦查询返回的可能有重复记录时,
就可能出现这个
除了这些之外,还有很多查询数据字典库,执⾏计划过程中就发现不可能存在结果的⼀些提示信息
filtered
使⽤ explain extended 时会出现这个列, 5.7 之后的版本默认就有这个字段,不需要使⽤ explain
extended 了。这个字段表示存储引擎返回的数据在 server 层过滤后,剩下多少满⾜查询的记录数量的⽐
例,注意是百分⽐,不是具体记录数。
参考⽹站
https://segmentfault.com/a/1190000008131735
https://blog.csdn.net/rewiner120/article/details/70598797
⼆、 MySQL 锁篇
MySQL 锁介绍
按照锁的粒度来说, MySQL 主要包含三种类型(级别)的锁定机制:
mysql> explain select * from tuser where name= 'asd' ;
+----+-------------+-------+------+------------------+------------------+-----
----+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key |
key_len | ref | rows | Extra |
+----+-------------+-------+------+------------------+------------------+-----
----+-------+------+-----------------------+
| 1 | SIMPLE | tuser | ref | idx_name_age_sex | idx_name_age_sex | 303
| const | 1 | Using index condition |
+----+-------------+-------+------+------------------+------------------+-----
----+-------+------+-----------------------+ 按照锁的功能来说分为: 共享读锁 排他写锁
按照锁的实现⽅式分为: 悲观锁 乐观锁 (使⽤某⼀版本列或者唯⼀列进⾏逻辑控制)
表级锁和⾏级锁的区别:
MySQL 表级锁
表级锁介绍
MySQL SQL layer 层实现
MySQL 的表级锁有两种:
MySQL 实现的表级锁定的争⽤状态变量:
- 全局锁:锁的是整个 database 。由 MySQL SQL layer 层实现的
- 表级锁:锁的是某个 table 。由 MySQL SQL layer 层实现的
- ⾏级锁:锁的是某⾏数据,也可能锁定⾏之间的间隙。由某些存储引擎实现,⽐如 InnoDB
表级锁:开销⼩,加锁快;不会出现死锁;锁定粒度⼤,发⽣锁冲突的概率最⾼,并发度最低;
⾏级锁:开销⼤,加锁慢;会出现死锁;锁定粒度最⼩,发⽣锁冲突的概率最低,并发度也最⾼;
⼀种是表锁。
⼀种是元数据锁( meta data lock MDL)
mysql> show status like 'table%'; 表锁介绍
表锁有两种表现形式:
⼿动增加表锁
查看表锁情况
删除表锁
表锁演示
环境准备
- table_locks_immediate :产⽣表级锁定的次数;
- table_locks_waited :出现表级锁定争⽤⽽发⽣等待的次数;
表共享读锁( Table Read Lock
表独占写锁( Table Write Lock
lock table 表名称 read ( write ), 表名称 2 read ( write ) ,其他 ;
show open tables ;
unlock tables ; 读锁演示
1 、表读锁
session1 Navicat )、 session2 mysql
-- 新建表
CREATE TABLE mylock (
id int ( 11 ) NOT NULL AUTO_INCREMENT,
NAME varchar ( 20 ) DEFAULT NULL ,
PRIMARY KEY (id)
);
INSERT INTO mylock (id,NAME) VALUES ( 1 , 'a' );
INSERT INTO mylock (id,NAME) VALUES ( 2 , 'b' );
INSERT INTO mylock (id,NAME) VALUES ( 3 , 'c' );
INSERT INTO mylock (id,NAME) VALUES ( 4 , 'd' ); 1 session1: lock table mylock read; -- mylock 表加读锁
2 session1: select * from mylock; -- 可以查询
3 session1 select * from tdep; -- 不能访问⾮锁定表
4 session2 select * from mylock; -- 可以查询 没有锁
5 session2 update mylock set name= 'x' where id= 2 ; -- 修改阻塞 , ⾃动加⾏写锁
6 session1 unlock tables; -- 释放表锁
7 session2 Rows matched: 1 Changed: 1 Warnings: 0 -- 修改执⾏完成
8 session1 select * from tdep; -- 可以访问
2 、表写锁
session1 Navicat )、 session2 mysql 课堂主题
Mysql 元数据锁、⾏锁、 MySQL 事务介绍、 InnoDB 架构
课堂⽬标
理解 MySQL 元数据锁的意义和使⽤场景
理解 MySQL ⾏锁的意义和使⽤场景
掌握 MySQL 记录锁和间隙锁的使⽤区别
掌握死锁的原理和死锁场景
理解事务的概念和四⼤特征( ACID
掌握 InnoDB 的架构和组件作⽤
理解预写机制、双写机制、 RedoLog UndoLog 的作⽤和⽇志落盘
知识要点
元数据锁介绍
MDL 不需要显式使⽤,在访问⼀个表的时候会被⾃动加上 MDL 的作⽤是,保证读写的正确性。你可
以想象⼀下,如果⼀个查询正在遍历⼀个表中的数据,⽽执⾏期间另⼀个线程对这个表结构做变更,删
了⼀列,那么查询线程拿到的结果跟表结构对不上,肯定是不⾏的。
因此, MySQL 5.5 版本中引⼊了 MDL 当对⼀个表做增删改查操作的时候,加 MDL 读锁 当要对
表做结构变更操作的时候,加 MDL 写锁
读锁之间不互斥,因此你可以有多个线程同时对⼀张表增删改查。
读写锁之间、写锁之间是互斥的,⽤来保证变更表结构操作的安全性。因此,如果有两个线程要同
时给⼀个表加字段,其中⼀个要等另⼀个执⾏完才能开始执⾏。
元数据锁演示
1 session1: lock table mylock write; -- mylock 表加写锁
2 session1: select * from mylock; -- 可以查询
3 session1 select * from tdep; -- 不能访问⾮锁定表
4 session1 update mylock set name= 'y' where id= 2 ; -- 可以执⾏
5 session2 select * from mylock; -- 查询阻塞
6 session1 unlock tables; -- 释放表锁
7 session2 4 rows in set ( 22.57 sec) -- 查询执⾏完成
8 session1 select * from tdep; -- 可以访问 session1 Navicat )、 session2 mysql
1 session1: begin ; -- 开启事务
select * from mylock; -- MDL 读锁
2 session2: alter table mylock add f int ; -- 修改阻塞
3 session1 commit; -- 提交事务 或者 rollback 释放读锁
4 session2 Query OK, 0 rows affected ( 38.67 sec) -- 修改完成
Records: 0 Duplicates: 0 Warnings: 0
我们可以看到 session A 先启动,这时候会对表 t 加⼀个 MDL 读锁。由于 session B 需要的也是 MDL
读锁,因此可以正常执⾏。
之后 session C 会被 blocked ,是因为 session A MDL 读锁还没有释放,⽽ session C 需要 MDL
锁,因此只能被阻塞。
如果只有 session C ⾃⼰被阻塞还没什么关系,但是之后所有要在表 t 上新申请 MDL 读锁的请求也会
session C 阻塞。前⾯我们说了,所有对表的增删改查操作都需要先申请 MDL 读锁,就都被锁住,
等于这个表现在完全不可读写了。 你现在应该知道了,事务中的 MDL 锁,在语句执⾏开始时申请,但是语句结束后并不会⻢上释放,⽽
会等到整个事务提交后再释放。
MySQL ⾏级锁
⾏级锁介绍
MySQL 的⾏级锁,是由存储引擎来实现的,利⽤存储引擎锁住索引项来实现的。这⾥我们主要讲解
InnoDB 的⾏级锁。
InnoDB 的⾏级锁,按照锁定范围来说,分为三种:
InnoDB 的⾏级锁,按照功能来说,分为两种: RR
对于 UPDATE DELETE INSERT 语句, InnoDB 会⾃动给涉及数据集加排他锁( X)
对于普通 SELECT 语句, InnoDB 不会加任何锁,事务可以通过以下语句显示给记录集加共享锁或排他
锁。 ⼿动添加共享锁( S ):
⼿动添加排他锁( x ):
InnoDB 也实现了表级锁,也就是意向锁,意向锁是 mysql 内部使⽤的,不需要⽤户⼲预。
- 记录锁( Record Locks : 锁定索引中⼀条记录。 id=1
- 间隙锁( Gap Locks : 要么锁住索引记录中间的值,要么锁住第⼀个索引记录前⾯的值或者最后⼀个索
引记录后⾯的值。
- Next-Key Locks: 是索引记录上的记录锁和在索引记录之前的间隙锁的组合。
- 共享锁( S ):允许⼀个事务去读⼀⾏,阻⽌其他事务获得相同数据集的排他锁。
- 排他锁( X ):允许获得排他锁的事务更新数据,阻⽌其他事务取得相同数据集的共享读锁(不是读)
和排他写锁。
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
SELECT * FROM table_name WHERE ... FOR UPDATE 共享锁
S
排他锁
X
意向共享锁
IS
意向排他锁
IX
共享锁( S
兼容
冲突
兼容
冲突
排他锁( X
冲突
冲突
冲突
冲突
意向共享锁( IS
兼容
冲突
兼容
兼容
意向排他锁
IX
冲突
冲突
兼容
兼容
意向锁和⾏锁可以共存,意向锁的主要作⽤是为了【全表更新数据】时的性能提升。否则在全表更
新数据时,需要先检索该表是否某些记录上⾯有⾏锁。
InnoDB ⾏锁 是通过给索引上的 索引项加锁来实现的 ,因此 InnoDB 这种⾏锁实现特点意味着:只
有通过索引条件检索的数据, InnoDB 才使⽤⾏级锁,否则, InnoDB 将使⽤表锁!
Innodb 所使⽤的 ⾏级锁定 争⽤状态查看:
- 意向共享锁( IS ):事务打算给数据⾏加⾏共享锁,事务在给⼀个数据⾏加共享锁前必须先取得该表的
IS 锁。
- 意向排他锁( IX ):事务打算给数据⾏加⾏排他锁,事务在给⼀个数据⾏加排他锁前必须先取得该表的
IX 锁。
mysql> show status like 'innodb_row_lock%' ; 对于这 5 个状态变量,⽐较重要的主要是:
尤其是当等待次数很⾼,⽽且每次等待时⻓也不⼩的时候,我们就需要分析系统中为什么会有如此多的
等待,然后根据分析结果着⼿指定优化计划。
两阶段锁
传统 RDBMS 加锁的⼀个原则,就是 2PL (Two-Phase Locking ,⼆阶段锁 ) 。相对⽽⾔, 2PL ⽐较容易理
解,说的是锁操作分为两个阶段:加锁阶段与解锁阶段,并且保证加锁阶段与解锁阶段不相交。下⾯,
仍旧以 MySQL 为例,来简单看看 2PL MySQL 中的实现。
- Innodb_row_lock_current_waits :当前正在等待锁定的数量;
- Innodb_row_lock_time :从系统启动到现在锁定总时间⻓度;
- Innodb_row_lock_time_avg :每次等待所花平均时间;
- Innodb_row_lock_time_max :从系统启动到现在等待最常的⼀次所花的时间;
- Innodb_row_lock_waits :系统启动后到现在总共等待的次数;
- Innodb_row_lock_time_avg (等待平均时⻓)
- Innodb_row_lock_waits (等待总次数)
- Innodb_row_lock_time (等待总时⻓)这三项。 从上图可以看出, 2PL 就是将加锁 / 解锁分为两个完全不相交的阶段。
InnoDB ⾏锁演示
⾏读锁
session1 Navicat )、 session2 mysql
加锁阶段:只加锁,不放锁。
解锁阶段:只放锁,不加锁。 ⾏读锁升级为表锁
session1 Navicat )、 session2 mysql
⾏写锁
session1 Navicat )、 session2 mysql
查看⾏锁状态 show STATUS like 'innodb_row_lock%' ;
1 session1: begin ; -- 开启事务未提交
select * from mylock where ID= 1 lock in share mode; -- ⼿动加 id=1 的⾏
读锁 , 使⽤索引
2 session2 update mylock set name= 'y' where id= 2 ; -- 未锁定该⾏可以修改
3 session2 update mylock set name= 'y' where id= 1 ; -- 锁定该⾏修改阻塞
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
-- 锁定超时
4 session1: commit; -- 提交事务 或者 rollback 释放读锁
5 session2 update mylock set name= 'y' where id= 1 ; -- 修改成功
Query OK, 1 row affected ( 0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
注:使⽤索引加⾏锁 ,未锁定的⾏可以访问
1 session1: begin ; -- 开启事务未提交
-- ⼿动加 name='c' 的⾏读锁 , 未使⽤索引
select * from mylock where name= 'c' lock in share mode;
2 session2 update mylock set name= 'y' where id= 2 ; -- 修改阻塞 未⽤索引⾏锁升级为表
3 session1: commit; -- 提交事务 或者 rollback 释放读锁
4 session2 update mylock set name= 'y' where id= 2 ; -- 修改成功
Query OK, 1 row affected ( 0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
注:未使⽤索引⾏锁升级为表锁 间隙锁
间隙锁有两种情况
1 、防⽌插⼊间隙内的数据
2 、防⽌已有数据更新为间隙内的数据
1 session1: begin ; -- 开启事务未提交
-- ⼿动加 id=1 的⾏写锁 ,
select * from mylock where id= 1 for update ;
2 session2 select * from mylock where id= 2 ; -- 可以访问
3 session2: select * from mylock where id= 1 ; -- 可以读 不加锁
4 session2: select * from mylock where id= 1 lock in share mode ; -- 加读
锁被阻塞
5 session1 commit; -- 提交事务 或者 rollback 释放写锁
5 session2 :执⾏成功
主键索引产⽣记录锁 session1 Navicat )、 session2 mysql
案例演示:
mysql> create table news (id int , number int ,primary key (id));
mysql> insert into news values ( 1 , 2 );
......
-- 加⾮唯⼀索引
mysql> alter table news add index idx_num(number);
session 1 :
start transaction ;
select * from news where number= 4 for update ;
session 2 :
start transaction ;
insert into news value( 2 , 4 );# (阻塞)
insert into news value( 2 , 2 );# (阻塞)
insert into news value( 4 , 4 );# (阻塞)
insert into news value( 4 , 5 );# (阻塞)
insert into news value( 7 , 5 );# (执⾏成功)
insert into news value( 9 , 5 );# (执⾏成功)
insert into news value( 11 , 5 );# (执⾏成功)
````
注: id number 都在间隙内则阻塞。
session 1 :
start transaction ;
select * from news where number= 13 for update ;
select * from news where id> 1 and id < 8 for update ;
session 2 :
start transaction ;
insert into news value( 11 , 5 );#( 执⾏成功 )
insert into news value( 12 , 11 );#( 执⾏成功 )
insert into news value( 14 , 11 );#( 阻塞 )
insert into news value( 15 , 12 );#( 阻塞 )
检索条件 number= 13 , 向左取得最靠近的值 11 作为左区间,向右由于没有记录因此取得⽆穷⼤作为右区间,
因此, session 1 的间隙锁的范围( 11 ,⽆穷⼤)
注:⾮主键索引产⽣间隙锁,主键范围
三、 MySQL 分库分表篇 三、 MySQL 分库分表篇
传统项⽬结构
数据库性能瓶颈:
1 、数据库连接数有限
MySQL 数据库默认 100 个连接、单机最⼤ 1500 连接。
2 、表数据量
1 、表数量多,成百上千
2 、单表数据,千万级别
3 、索引,命中率问题,索引存磁盘,占空间
3 、硬件问题
性能指标:单表 QPS TPS
数据库性能优化
1 、参数优化
2 、缓存、索引
3 、读写分离 4 、分库分表
分库分表介绍
使⽤背景
当【表的数量】达到了⼏百上千张表时,众多的业务模块都访问这个数据库,压⼒会⽐较⼤,考虑
对其进⾏分库。
当【表的数据】达到了⼏千万级别,在做很多操作都⽐较吃⼒ , 所以,考虑对其进⾏分库或者分表
数据切分( sharding )⽅案
数据的切分( Sharding )根据其切分规则的类型,可以分为两种切分模式:
垂直切分: 按照业务模块进⾏切分,将不同模块的表切分到不同的数据库中。
⽔平切分: 将⼀张⼤表按照⼀定的切分规则,按照 切分成不同的表或者切分到不同的库中。
⽔平切分规则
常⽤的切分规则有以下⼏种:
按照 ID 取模: ID 进⾏取模,余数决定该⾏数据切分到哪个表或者库中
按照⽇期: 按照年⽉⽇,将数据切分到不同的表或者库中
按照范围: 可以对某⼀列按照范围进⾏切分,不同的范围切分到不同的表或者数据库中。
切分原则
第⼀原则: 能不切分尽量不要切分。 第⼆原则: 如果要切分⼀定要选择合适的切分规则,提前规划好。
第三原则: 数据切分尽量通过数据冗余或表分组( Table Group )来 降低跨库 Join 的可能。
第四原则: 由于数据库中间件对数据 Join 实现的优劣难以把握,⽽且实现⾼性能难度极⼤,业务
读取尽量少使⽤多表 Join
分库分表需要解决的问题
分布式事务问题
强⼀致性事务(同步)
最终⼀致性事务(异步思想) 利⽤记录⽇志等⽅式
分布式主键 ID 问题
redis incr 命令
数据库(⽣成主键)
UUID
snowflake 算法( https://www.sohu.com/a/232008315_453160
跨库 join 问题
通过业务分析,将不同库的 join 查询拆分成多个 select
建⽴全局表(每个库都有⼀个相同的表)
冗余字段(不符合数据库三范式)
E-R 分⽚(将有 ER 关系的记录都存储到⼀个库中)
最多⽀持跨两张表跨库的 join
跨库 count order by group by 问题
分库分表实现技术
阿⾥的 TDDL Cobar
基于阿⾥ Cobar 开发的 Mycat
当当⽹的 sharding-jdbc
课堂主题
MyCat 介绍、分⽚规则、案例展示
课堂⽬标
理解 MyCat 的架构和原理
本地事务: ACID
分布式事务:根据百度百科的定义, CAP 定理⼜称 CAP 原则,指的是在⼀个分布式系统中,
Consistency (⼀致性)、 Availability (可⽤性)、 Partition tolerance (分区容错性)。⼀
致性是强⼀致性。 CAP 理论最多只能同时满⾜两个。
BASE :基本可⽤ + 软状态 + 最终⼀致性 会安装和启动 MyCat
会配置 MyCat
熟悉常⻅分⽚规则和优劣
掌握 mycat 分库分表的⽅法
Mycat 介绍
什么是 Mycat
官⽅⽹站: http://www.mycat.org.cn/ http://www.mycat.io/
db proxy Mycat
Mycat 架构
Mycat 核⼼概念 Schema :由它指定逻辑数据库(相当于 MySQL database 数据库)
Table :逻辑表(相当于 MySQL table 表)
DataNode :真正存储数据的物理节点
DataHost :存储节点所在的数据库主机(指定 MySQL 数据库的连接信息)
User MyCat 的⽤户(类似于 MySQL 的⽤户,⽀持多⽤户)
Mycat 主要解决的问题
海量数据存储
查询优化
Mycat 对多数据库的⽀持
Mycat 分⽚策略
MyCAT ⽀持⽔平分⽚与垂直分⽚:
⽔平分⽚:⼀个表格的数据分割到多个节点上,按照⾏分隔。
垂直分⽚:⼀个数据库中多个表格 A B C A 存储到节点 1 上, B 存储到节点 2 上, C 存储到节点 3
上。 MyCAT 通过定义表的分⽚规则来实现分⽚,每个表格可以捆绑⼀个分⽚规则,每个分⽚规则指定⼀个分
⽚字段并绑定⼀个函数,来实现动态分⽚算法。
1. Schema :逻辑库,与 MySQL 中的 Database (数据库)对应,⼀个逻辑库中定义了所包括的 Table
2. Table :表,即物理数据库中存储的某⼀张表,与传统数据库不同,这⾥的表格需要声明其所存储的
逻辑数据节点 DataNode 在此可以指定表的分⽚规则。 3. DataNode MyCAT 的逻辑数据节点,是存放 table 的具体物理节点,也称之为分⽚节点,通过
DataHost 来关联到后端某个具体数据库上
4. DataHost :定义某个物理库的访问地址,⽤于捆绑到 Datanode
Mycat 安装
注意:需要先安装 jdk (操作系统如果是 64 位,必须安装 64 位的 JDK
第⼀步:下载 MyCat
第⼆步:解压缩,得到 mycat ⽬录
第三步:进⼊ mycat/bin ,启动 MyCat
第四步:访问 Mycat
Mycat 分⽚
配置 schema.xml
schema.xml 介绍
schema.xml 作为 Mycat 中重要的配置⽂件之⼀, 管理着 Mycat 的逻辑库、表、分⽚规则、 DataNode
以及 DataHost 之间的映射关系。 弄懂这些配置,是正确使⽤ Mycat 的前提。
schema 标签⽤于定义 MyCat 实例中的逻辑库
Table 标签定义了 MyCat 中的逻辑表
dataNode 标签定义了 MyCat 中的数据节点,也就是我们通常说所的数据分⽚。
dataHost 标签在 mycat 逻辑库中也是作为最底层的标签存在,直接定义了具体的数据库实例、读写
分离配置和⼼跳语句。
schema.xml 配置
wget http://dl.mycat.io/1.6-RELEASE/Mycat-server-1.6-RELEASE-20161028204710-
linux.tar.gz
tar -zxvf Mycat-server-1.6-RELEASE-20161028204710-linux.tar.gz
- 启动命令: ./mycat start
- 停⽌命令: ./mycat stop
- 重启命令: ./mycat restart
- 查看状态: ./mycat status
使⽤ mysql 的客户端直接连接 mycat 服务。默认服务端⼝为【 8066
mysql -uroot -p123456 -h127.0.0.1 -P8066 配置 Server.xml
server.xml 介绍
server.xml ⼏乎保存了所有 mycat 需要的系统配置信息。最常⽤的是在此配置⽤户名、密码及权限。
server.xml 配置
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat = "http://io.mycat/" >
<!--
schema : 逻辑库 name: 逻辑库名称
sqlMaxLimit :⼀次取多少条数据 要超过⽤ limit xxx
table: 逻辑表
dataNode: 数据节点 对应 datanode 标签
rule :分⽚规则,对应 rule.xml
subTables: ⼦表
primaryKey :分⽚主键 可缓存
-->
<schema name = "TESTDB" checkSQLschema = "false" sqlMaxLimit = "100" >
<!-- auto sharding by id (long) -->
<table name = "item" dataNode = "dn1,dn2,dn3" rule = "mod-long"
primaryKey = "ID" />
</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" />
<!--
dataHost : 数据主机(节点主机)
balance 1 :读写分离 0 : 读写不分离
writeType 0 第⼀个 writeHost 写, 1 随机 writeHost
dbDriver : 数据库驱动 native MySQL JDBC Oracle SQLServer
switchType : 是否主动读
1 、主从⾃动切换 -1 不切换 2 当从机延时超过 slaveThreshold 值时切换为主读
-->
<dataHost name = "localhost1" maxCon = "1000" minCon = "10" balance = "0"
writeType = "0" dbType = "mysql" dbDriver = "native" switchType = "1"
slaveThreshold = "100" >
<heartbeat> select user() </heartbeat>
<writeHost host = "hostM1" url = "192.168.24.129:3306" user = "root"
password = "root" >
</writeHost>
</dataHost>
</mycat:schema> 配置 rule.xml
rule.xml ⾥⾯就定义了我们对表进⾏拆分所涉及到的规则定义。我们可以灵活的对表使⽤不同的分⽚算
法,或者对表使⽤相同的算法但具体的参数不同。这个⽂件⾥⾯主要有 tableRule function 这两个标
签。在具体使⽤过程中可以按照需求添加 tableRule function
此配置⽂件可以不⽤修改,使⽤默认即可。
tableRule 标签配置说明:
name 属性指定唯⼀的名字,⽤于标识不同的表规则
rule 标签则指定对物理表中的哪⼀列进⾏拆分和使⽤什么路由算法。
columns 内指定要拆分的列名字。
algorithm 使⽤ function 标签中的 name 属性。连接表规则和具体路由算法。当然,多个表规则
可以连接到同⼀个路由算法上。 table 标签内使⽤。让逻辑表使⽤这个规则进⾏分⽚。
function 标签配置说明:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat = "http://io.mycat/" >
<system>
<property name = "defaultSqlParser" > druidparser </property>
</system>
<user name = "mycat" >
<property name = "password" > mycat </property>
<property name = "schemas" > TESTDB </property>
</user>
</mycat:server>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:rule SYSTEM "rule.dtd">
<mycat:rule xmlns:mycat = ”http://io.mycat/“ >
<tableRule name = "sharding-by-intfile" >
<rule>
<columns> sharding_id </columns>
<algorithm> hash-int </algorithm>
</rule>
</tableRule>
<function name = "hash-int"
class = "io.mycat.route.function.PartitionByFileMap" >
<property name = "mapFile" > partition-hash-int.txt </property>
</function>
</mycat:rule> name 指定算法的名字。
class 制定路由算法具体的类名字。
property 为具体算法需要⽤到的⼀些属性。
路由算法的配置可以查看算法章节。
⼗个常⽤的分⽚规则
连续分⽚
⼀、⽇期列分区法
<!-- 按固定时间分⽚ -->
<tableRule name = "sharding-by-date" >
<rule>
<columns> create_time </columns>
<algorithm> sharding-by-date </algorithm>
</rule>
</tableRule>
<function name = "sharding-by-date"
class = "io.mycat.route.function..PartitionByDate" >
<property name = "dateFormat" > yyyy-MM-dd </property>
<property name = "sBeginDate" > 2014-01-01 </property>
<property name = "sPartionDay" > 10 </property>
</function>
<!-- 按⾃然⽉分⽚ -->
<tableRule name = "sharding-by-month" >
<rule>
<columns> create_time </columns>
<algorithm> sharding-by-month </algorithm>
</rule>
</tableRule>
<function name = "sharding-by-month"
class = "io.mycat.route.function..PartitionByMonth" >
<property name = "dateFormat" > yyyy-MM-dd </property>
<property name = "sBeginDate" > 2014-01-01 </property>
</function>
<!--
按单⽉⼩时分⽚
适合做⽇志,每⽉末,⼿⼯清理
-->
<tableRule name = "sharding-by-hour" >
<rule>
<columns> create_time </columns>
<algorithm> sharding-by-hour </algorithm>
</rule>
</tableRule>
<function name = "sharding-by-hour"
class = "io.mycat.route.function..LastestMonthPartition" >
<property name = "splitOneDay" > 24 </property> 配置说明:
tableRule 标签:
columns :标识将要分⽚的表字段
algorithm :指定分⽚函数
function 标签:
dateFormat :⽇期格式
sBeginDate :开始⽇期
sPartionDay :分区天数,即默认从开始⽇期算起,分隔 10 天⼀个分区
⼆、范围约定
配置说明:
tableRule 标签:
columns :标识将要分⽚的表字段
algorithm :指定分⽚函数
function 标签:
mapFile :指定分⽚函数需要的配置⽂件名称
autopartition-long.txt ⽂件内容:
所有的节点配置都是从 0 开始,及 0 代表节点 1 ,此配置⾮常简单,即预先制定可能的 id 范围对应某个分
</function>
<tableRule name = "auto-sharding-long" >
<rule>
<columns> user_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>
</function> 优势:扩容⽆需迁移数据
缺点:热点数据,并发受限
离散分⽚
⼀、枚举法
配置说明:
tableRule 标签:
columns :标识将要分⽚的表字段
algorithm :指定分⽚函数
function 标签:
mapFile :指定分⽚函数需要的配置⽂件名称
type :默认值为 0 0 表示 Integer ,⾮零表示 String
defaultNode :指定默认节点,⼩于 0 表示不设置默认节点,⼤于等于 0 表示设置默认节点, 0 代表节
1
默认节点的作⽤:枚举分⽚时,如果碰到不识别的枚举值,就让它路由到默认节点。
如果不配置默认节点( defaultNode 值⼩于 0 表示不配置默认节点),碰到不识别的枚举值就
# range start-end ,data node index
# K=1000,M=10000.
0-500M=0 0-100 0
500M-1000M=1 101-200 1
201-300 2
1000M-1500M=2
default=0
# 或以下写法
# 0-10000000=0
# 10000001-20000000=1
<tableRule name = "sharding-by-intfile" >
<rule>
<columns> user_id </columns>
<algorithm> hash-int </algorithm>
</rule>
</tableRule>
<function name = "hash-int"
class = "io.mycat.route.function.PartitionByFileMap" >
<property name = "mapFile" > partition-hash-int.txt </property>
<property name = "type" > 0 </property>
<property name = "defaultNode" > 0 </property>
</function> 会报错:
can't find datanode for sharding column:column_name val:ffffffff
partition-hash-int.txt 配置:
⼆、求模法
此种配置⾮常明确,即根据 id count (你的结点数)进⾏求模运算,相⽐⽅式 1 ,此种在批量插⼊时需
要切换数据源, id 不连续
配置说明:
tableRule 标签:
columns :标识将要分⽚的表字段
algorithm :指定分⽚函数
function 标签:
count :节点数量
三、字符串拆分 hash 解析
10000=0 列等于 10000 放第⼀个分⽚
10010=1
=0
=1
beijing=0
tianjin=1
zhanghai=2
<tableRule name = "mod-long" >
<rule>
<columns> user_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> <tableRule name = "sharding-by-stringhash" >
<rule>
<columns> user_id </columns>
<algorithm> sharding-by-stringhash </algorithm>
</rule>
</tableRule>
<function name = "sharding-by-stringhash"
class = "io.mycat.route.function.PartitionByString" >
<property name = "length" > 512 </property> <!-- zero-based -->
<property name = "count" > 2 </property>
<property name = "hashSlice" > 0:2 </property>
</function>
配置说明:
tableRule 标签:
columns :标识将要分⽚的表字段
algorithm :指定分⽚函数
function 标签:
length :代表字符串 hash 求模基数
count :分区数
hashSlice hash 预算位,即根据⼦字符串 hash 运算
"2" -> (0,2)
"1:2" -> (1,2
"1:" -> (1,0)
"-1:" -> (-1,0)
":-1" -> (0,-1)
":" -> (0,0)
public class PartitionByStringTest {
@Test
public void test () {
PartitionByString rule = new PartitionByString ();
String idVal = null ;
rule . setPartitionLength ( "512" );
rule . setPartitionCount ( "2" );
rule . init (); 四、固定分⽚ hash 算法
配置说明:
tableRule 标签:
columns :标识将要分⽚的表字段
algorithm :指定分⽚函数
function 标签:
partitionCount :指定分⽚个数列表
partitionLength : 分⽚范围列表,分区⻓度 : 默认为最⼤ 2^n=1024 , 即最⼤⽀持 1024 分区
rule . setHashSlice ( "0:2" );
// idVal = "0";
// Assert.assertEquals(true, 0 == rule.calculate(idVal));
// idVal = "45a";
// Assert.assertEquals(true, 1 == rule.calculate(idVal));
//last 4
rule = new PartitionByString ();
rule . setPartitionLength ( "512" );
rule . setPartitionCount ( "2" );
rule . init ();
//last 4 characters
rule . setHashSlice ( "-4:0" );
idVal = "aaaabbb0000" ;
Assert . assertEquals ( true , 0 == rule . calculate ( idVal ));
idVal = "aaaabbb2359" ;
Assert . assertEquals ( true , 0 == rule . calculate ( idVal ));
}
<tableRule name = "rule1" >
<rule>
<columns> user_id </columns>
<algorithm> func1 </algorithm>
</rule>
</tableRule>
<function name = "func1"
class = "io.mycat.route.function.PartitionByLong" >
<property name = "partitionCount" > 2,1 </property>
<property name = "partitionLength" > 256,512 </property>
</function> 约束 :
count,length 两个数组的⻓度必须是⼀致的。 1024 = sum((count[i]*length[i]))
⽤法例⼦:
如果需要平均分配设置:平均分为 4 分⽚, partitionCount*partitionLength=1024
五、⼀致性 hash
@Test
public void testPartition () {
// 本例的分区策略:希望将数据⽔平分成 3 份,前两份各占 25% ,第三份占 50% 。(故本例⾮均匀分
区)
// |<---------------------1024------------------------>|
// |<----256--->|<----256--->|<----------512---------->|
// | partition0 | partition1 | partition2 |
// | 2 , count[0]=2 | 1 份,故 count[1]=1 |
int [] count = new int [] { 2 , 1 };
int [] length = new int [] { 256 , 512 };
PartitionUtil pu = new PartitionUtil ( count , length );
// 下⾯代码演示分别以 offerId 字段或 memberId 字段根据上述分区策略拆分的分配结果
int DEFAULT_STR_HEAD_LEN = 8 ; // cobar 默认会配置为此值
long offerId = 12345 ;
String memberId = "qiushuo" ;
// 若根据 offerId 分配, partNo1 将等于 0 ,即按照上述分区策略, offerId 12345 时将会被分
配到 partition0
int partNo1 = pu . partition ( offerId );
// 若根据 memberId 分配, partNo2 将等于 2 ,即按照上述分区策略, memberId qiushuo 时将会
被分到 partition2
int partNo2 = pu . partition ( memberId , 0 , DEFAULT_STR_HEAD_LEN );
Assert . assertEquals ( 0 , partNo1 );
Assert . assertEquals ( 2 , partNo2 );
}
<function name = "func1"
class = "org.opencloudb.route.function.PartitionByLong" >
<property name = "partitionCount" > 4 </property>
<property name = "partitionLength" > 256 </property>
</function>
<tableRule name = "sharding-by-murmur" >
<rule> ⼀致性 hash 预算有效解决了分布式数据的扩容问题,前 1-9 id 规则都多少存在数据扩容难题,⽽ 10
则解决了数据扩容难点
六、编程指定
<columns> user_id </columns>
<algorithm> murmur </algorithm>
</rule>
</tableRule>
<function name = "murmur"
class = "io.mycat.route.function.PartitionByMurmurHash" >
<!-- 默认是 0 -->
<property name = "seed" > 0 </property>
<!-- 要分⽚的数据库节点数量,必须指定,否则没法分⽚ -->
<property name = "count" > 2 </property>
<!-- ⼀个实际的数据库节点被映射为这么多虚拟节点,默认是 160 倍,也就是虚拟节点数是物理节点
数的 160 -->
<property name = "virtualBucketTimes" > 160 </property>
<!-- <property name="weightMapFile">weightMapFile</property> 节点的权重,没有指
定权重的节点默认是 1 。以 properties ⽂件的格式填写,以从 0 开始到 count-1 的整数值也就是节点索引
key ,以节点权重值为值。所有权重值必须是正整数,否则以 1 代替 -->
<!-- <property name="bucketMapPath">/etc/mycat/bucketMapPath</property>
⽤于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的 murmur
hash 值与物理节点的映射按⾏输出到这个⽂件,没有默认值,如果不指定,就不会输出任何东⻄ -->
</function> 配置说明:
tableRule 标签:
columns :标识将要分⽚的表字段
algorithm :指定分⽚函数
function 标签:
startIndex :字符串截取的起始索引位置
size :截取的位数
partitionCount :分区数量
defaultPartition :默认分区
11010419800101
此⽅法为直接根据字符⼦串(必须是数字)计算分区号(由应⽤传递参数,显式指定分区号)。
例如 id=05-100000002
在此配置中代表根据 id 中从 startIndex=0 ,开始,截取 siz=2 位数字即 05 05 就是获取的分区,如果没传
默认分配到 defaultPartition
优点:数据分布均匀,并发能⼒强
缺点:移植性差、扩容性差
综合分⽚
⼀、通配取模
<tableRule name = "sharding-by-substring" >
<rule>
<columns> user_id </columns>
<algorithm> sharding-by-substring </algorithm>
</rule>
</tableRule>
<function name = "sharding-by-substring"
class = "io.mycat.route.function.PartitionDirectBySubString" >
<property name = "startIndex" > 0 </property> <!-- zero-based -->
<property name = "size" > 2 </property>
<property name = "partitionCount" > 8 </property>
<property name = "defaultPartition" > 0 </property>
</function> 配置说明:
tableRule 标签:
columns :标识将要分⽚的表字段
algorithm :指定分⽚函数
function 标签:
patternValue :求模基数
defaultNode :默认节点,如果不配置了默认,则默认是 0 即第⼀个结点
mapFile :配置⽂件路径
partition-pattern.txt ⽂件内容 :
配置⽂件中, 1-32 即代表 id%256 后分布的范围,如果在 1-32 则在分区 1 ,其他类推,如果 id ⾮数字数
据,则会分配在 defaultNode 默认节点
⼆、 ASCII 码求模通配
<tableRule name = "sharding-by-pattern" >
<rule>
<columns> user_id </columns>
<algorithm> sharding-by-pattern </algorithm>
</rule>
</tableRule>
<function name = "sharding-by-pattern"
class = "io.mycat.route.function.PartitionByPattern" >
<property name = "patternValue" > 256 </property>
<property name = "defaultNode" > 2 </property>
<property name = "mapFile" > partition-pattern.txt </property>
</function>
# id partition range start-end ,data node index
###### first host configuration
1-32=0
33-64=1
65-96=2
97-128=3
######## second host configuration
129-160=4
161-192=5
193-224=6
225-256=7
0-0=7 <tableRule name = "sharding-by-prefixpattern" >
<rule>
<columns> user_id </columns>
<algorithm> sharding-by-prefixpattern </algorithm>
</rule>
</tableRule>
<function name = "sharding-by-pattern"
class = "io.mycat.route.function.PartitionByPrefixPattern" >
<property name = "patternValue" > 256 </property>
<property name = "prefixLength" > 5 </property>
<property name = "mapFile" > partition-pattern.txt </property>
</function>
配置说明:
tableRule 标签:
columns :标识将要分⽚的表字段
algorithm :指定分⽚函数
function 标签:
patternValue :求模基数
prefixLength ASCII 截取的位数
mapFile :配置⽂件路径
partition-pattern.txt ⽂件内容:
配置⽂件中, 1-32 即代表 id%256 后分布的范围,如果在 1-32 则在分区 1 ,其他类推
此种⽅式类似⽅式 6 ,只不过采取的是将列中前 prefixLength 位所有 ASCII 码的和与 patternValue 进⾏求
模,即 sum%patternValue , 获取的值在通配范围内的,即分⽚数。
ASCII 编码:
48-57=0-9 阿拉伯数字
64 65-90=@ A-Z
97-122=a-z
# range start-end ,data node index
# ASCII
# 48-57=0-9
# 64 65-90=@ A-Z
# 97-122=a-z
###### first host configuration
1-4=0
5-8=1
9-12=2
13-16=3
###### second host configuration 测试分⽚
需求
把商品表分⽚存储到三个数据节点上。
创建表
配置完毕后,重新启动 mycat 。使⽤ mysql 客户端连接 mycat ,创建表。
17-20=4
21-24=5
25-28=6
29-32=7
0-0=7
CREATE TABLE item (
id int ( 11 ) NOT NULL ,
name varchar ( 20 ) DEFAULT NULL ,
PRIMARY KEY (id)
) ENGINE = InnoDB DEFAULT CHARSET =utf8; 分⽚测试
分⽚策略指定为 “auto-sharding-long”
分⽚规则指定为 “mod-long”
Mycat 读写分离
MyCat 的读写分离是建⽴在 MySQL 主从复制基础 之上实现的,所以必须先搭建 MySQL 的主从复制。
数据库读写分离对于⼤型系统或者访问量很⾼的互联⽹应⽤来说,是必不可少的⼀个重要功能。对于
MySQL 来说,标准的读写分离是主从模式,⼀个写节点 Master 后⾯跟着多个读节点,读节点的数量取
决于系统的压⼒,通常是 1-3 个读节点的配置
Mycat 实现的读写分离和⾃动切换机制,需要 mysql 的主从复制机制配合。
Mycat 配置
Mycat 1.4 ⽀持 MySQL 主从复制状态绑定的读写分离机制,让读更加安全可靠,配置如下:
<dataNode name = "dn1" dataHost = "localhost1" database = "db1" />
<dataNode name = "dn2" dataHost = "localhost1" database = "db2" />
<dataNode name = "dn3" dataHost = "localhost1" database = "db3" />
<dataHost name = "localhost1" maxCon = "1000" minCon = "10" balance = "1"
writeType = "0" dbType = "mysql" dbDriver = "native" switchType = "2"
slaveThreshold = "100" >
<heartbeat> show slave status </heartbeat>
<writeHost host = "hostM" url = "192.168.25.134:3306" user = "root"
password = "root" >
<readHost host = "hostS" url = "192.168.25.166:3306" user = "root"
password = "root" />
</writeHost>
</dataHost> (1) 设置 balance="1" writeType="0"
Balance 参数设置:
1. balance=“0”, 所有读操作都发送到当前可⽤的 writeHost 上。
2. balance=“1” ,所有读操作都随机的发送到 readHost
3. balance=“2” ,所有读操作都随机的在 writeHost readhost 上分发
WriteType 参数设置:
1. writeType=“0”, 所有写操作都发送到可⽤的 writeHost 上。
2. writeType=“1” ,所有写操作都随机的发送到 readHost
3. writeType=“2” ,所有写操作都随机的在 writeHost readhost 分上发。
“readHost 是从属于 writeHost 的,即意味着它从那个 writeHost 获取同步数据,因此,当它所属的
writeHost 宕机了,则它也不会再参与到读写分离中来,即 不⼯作了 ,这是因为此时,它的数据已
不可靠 了。基于这个考虑,⽬前 mycat 1.3 1.4 版本中,若想⽀持 MySQL ⼀主⼀从的标准配置,并
且在主节点宕机的情况下,从节点还能读取数据,则需要在 Mycat ⾥配置为两个 writeHost 并设置
banlance=1
(2) 设置 switchType="2" slaveThreshold="100"
switchType ⽬前有三种选择:
-1 :表示不⾃动切换
1 :默认值,⾃动切换
2 :基于 MySQL 主从同步的状态决定是否切换
Mycat ⼼跳检查语句配置为 show slave status dataHost 上定义两个新属性: switchType="2"
slaveThreshold="100" ,此时意味着开启 MySQL 主从复制状态绑定的读写分离与切换机制。 Mycat
跳机制通过检测 show slave status 中的 "Seconds_Behind_Master", "Slave_IO_Running",
"Slave_SQL_Running" 三个字段来确定当前主从同步的状态以及 Seconds_Behind_Master 主从复制时
延。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一条 大鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值