咱们知道,若是咱们使用mysql,当数据库数据量达到必定数据量以后,会考虑对数据库进行分库分表等操做,可是在什么状况下作怎么的切分,下面分表介绍。html
首先,在单台数据库服务器性能足够的状况下,分库对于数据库性能是没有影响的。在数据库存储上,database
只起到一个namespace
的做用。database
中的表文件存储在一个以database名
命名的文件夹中。好比下面的employees
数据库:mysql
mysql> show tables in employees;
+---------------------+
| Tables_in_employees |
+---------------------+
| departments |
| dept_emp |
| dept_manager |
| employees |
| salaries |
| titles |
+---------------------+
在操做系统中看是这样的:git
# haitian at haitian-coder.local in /usr/local/var/mysql/employees on git:master ● [21:19:47]
→ ls
db.opt dept_emp.frm dept_manager.ibd salaries.frm titles.ibd
departments.frm dept_emp.ibd employees.frm salaries.ibd
departments.ibd dept_manager.frm employees.ibd titles.frm
database
不是文件,只起到namespace
的做用,因此MySQL
对database
大小固然也是没有限制的,并且对里面的表数量也没有限制。github
因此,为何要分库呢?算法
答案是为了解决单台服务器的性能问题,当单台数据库服务器没法支撑当前的数据量时,就须要根据业务逻辑紧密程度把表分红几撮,分别放在不一样的数据库服务器中以下降单台服务器的负载。sql
分库通常考虑的是垂直切分,除非在垂直切分后,数据量仍然多到单台服务器没法负载,才继续水平切分。数据库
好比一个论坛系统的数据库因当前服务器性能没法知足须要进行分库。先垂直切分,按业务逻辑把用户相关数据表好比用户信息、积分、用户间私信等放入user数据库;论坛相关数据表好比板块,帖子,回复等放入forum数据库,两个数据库放在不一样服务器上。express
拆分后表每每不可能彻底无关联,好比帖子中的发帖人、回复人这些信息都在user数据库中。未拆分前可能一次联表查询就能获取当前帖子的回复、发帖人、回复人等全部信息,拆分后由于跨数据库没法联表查询,只能屡次查询得到最终数据。服务器
因此总结起来,分库的目的是下降单台服务器负载,切分原则是根据业务紧密程度拆分,缺点是跨数据库没法联表查询。函数
当数据量超大的时候,B-Tree索引就没法起做用了。除非是索引覆盖查询,不然数据库服务器须要根据索引扫描的结果回表,查询全部符合条件的记录,若是数据量巨大,这将产生大量随机I/O,随之,数据库的响应时间将大到不可接受的程度。另外,索引维护(磁盘空间、I/O操做)的代价也很是高。
缘由:
1.根据MySQL索引实现原理及相关优化策略的内容咱们知道Innodb
主索引叶子节点存储着当前行的全部信息,因此减小字段可以使内存加载更多行数据,有利于查询。
2.受限于操做系统中的文件大小限制。
切分原则: 把不经常使用或业务逻辑不紧密或存储内容比较多的字段分到新的表中可以使表存储更多数据。。
缘由:
1.随着数据量的增大,table行数巨大,查询的效率愈来愈低。
2.一样受限于操做系统中的文件大小限制,数据量不能无限增长,当到达必定容量时,须要水平切分以下降单表(文件)的大小。
切分原则: 增量区间或散列或其余业务逻辑。
使用哪一种切分方法要根据实际业务逻辑判断。
好比对表的访问可能是近期产生的新数据,历史数据访问较少,能够考虑根据时间增量把数据按照必定时间段(好比每一年)切分。
若是对表的访问较均匀,没有明显的热点区域,则能够考虑用范围(好比每500w一个表)或普通Hash或一致性Hash来切分。
全局主键问题:
本来依赖数据库生成主键(好比自增)的表在拆分后须要本身实现主键的生成,由于通常拆分规则是创建在主键上的,因此在插入新数据时须要肯定主键后才能找到存储的表。
实际应用中也已经有了比较成熟的方案。好比对于自增列作主键的表,flickr
的全局主键生成方案很好的解决了性能和单点问题,具体实现原理能够参考这个帖子。除此以外,还有相似于uuid的全局主键生成方案,好比达达参考的Instagram
的ID生成器。
一致性Hash:
使用一致性Hash切分比普通的Hash切分可扩展性更强,能够实现拆分表的添加和删除。一致性Hash的具体原理能够参考这个帖子,若是拆分后的表存储在不一样服务器节点上,能够跟帖子同样对节点名或ip取Hash;若是拆分后的表存在一个服务器中则可对拆分后的表名取Hash。
上面介绍的传统的分库分表都是在应用层实现,拆分后都要对原有系统进行很大的调整以适应新拆分后的库或表,好比实现一个SQL
中间件、本来的联表查询改为两次查询、实现一个全局主键生成器等等。
而下面介绍的MySQL
分区表是在数据库层面,MySQL
本身实现的分表功能,在很大程度上简化了分表的难度。
对用户来讲,分区表是一个独立的逻辑表,可是底层由多个物理子表实现。
也就是说,对于原表分区后,对于应用层来讲能够不作变化,咱们无需改变原有的SQL
语句,至关于MySQL
帮咱们实现了传统分表后的SQL
中间件,固然,MySQL
的分区表的实现要复杂不少。
另外,在建立分区时能够指定分区的索引文件和数据文件的存储位置,因此能够把数据表的数据分布在不一样的物理设备上,从而高效地利用多个硬件设备。
一些限制:
1.在5.6.7以前的版本,一个表最多有1024
个分区;从5.6.7开始,一个表最多能够有8192
个分区。
2.分区表中没法使用外键约束。
3.主表的全部惟一索引列(包括主键)都必须包含分区字段。MySQL
官方文档中写的是:
All columns used in the partitioning expression for a partitioned table must be part of every unique key that the table may have.
这句话不是很好理解,须要经过例子才能明白,MySQL
官方文档也为此限制特地作了举例和解释。
根据范围分区,范围应该连续可是不重叠,使用PARTITION BY RANGE
, VALUES LESS THAN
关键字。不使用COLUMNS
关键字时RANGE
括号内必须为整数字段名或返回肯定整数的函数。
根据数值范围:
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT NOT NULL,
store_id INT NOT NULL
)
PARTITION BY RANGE (store_id) (
PARTITION p0 VALUES LESS THAN (6),
PARTITION p1 VALUES LESS THAN (11),
PARTITION p2 VALUES LESS THAN (16),
PARTITION p3 VALUES LESS THAN MAXVALUE
);
根据TIMESTAMP
范围:
CREATE TABLE quarterly_report_status (
report_id INT NOT NULL,
report_status VARCHAR(20) NOT NULL,
report_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
PARTITION BY RANGE ( UNIX_TIMESTAMP(report_updated) ) (
PARTITION p0 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-01-01 00:00:00') ),
PARTITION p1 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-04-01 00:00:00') ),
PARTITION p2 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-07-01 00:00:00') ),
PARTITION p3 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-10-01 00:00:00') ),
PARTITION p4 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-01-01 00:00:00') ),
PARTITION p5 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-04-01 00:00:00') ),
PARTITION p6 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-07-01 00:00:00') ),
PARTITION p7 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-10-01 00:00:00') ),
PARTITION p8 VALUES LESS THAN ( UNIX_TIMESTAMP('2010-01-01 00:00:00') ),
PARTITION p9 VALUES LESS THAN (MAXVALUE)
);
添加COLUMNS
关键字可定义非integer范围及多列范围,不过须要注意COLUMNS
括号内只能是列名,不支持函数;多列范围时,多列范围必须呈递增趋势:
根据DATE
、DATETIME
范围:
CREATE TABLE members (
firstname VARCHAR(25) NOT NULL,
lastname VARCHAR(25) NOT NULL,
username VARCHAR(16) NOT NULL,
email VARCHAR(35),
joined DATE NOT NULL
)
PARTITION BY RANGE COLUMNS(joined) (
PARTITION p0 VALUES LESS THAN ('1960-01-01'),
PARTITION p1 VALUES LESS THAN ('1970-01-01'),
PARTITION p2 VALUES LESS THAN ('1980-01-01'),
PARTITION p3 VALUES LESS THAN ('1990-01-01'),
PARTITION p4 VALUES LESS THAN MAXVALUE
);
根据多列范围:
CREATE TABLE rc3 (
a INT,
b INT
)
PARTITION BY RANGE COLUMNS(a,b) (
PARTITION p0 VALUES LESS THAN (0,10),
PARTITION p1 VALUES LESS THAN (10,20),
PARTITION p2 VALUES LESS THAN (10,30),
PARTITION p3 VALUES LESS THAN (10,35),
PARTITION p4 VALUES LESS THAN (20,40),
PARTITION p5 VALUES LESS THAN (MAXVALUE,MAXVALUE)
);
根据具体数值分区,每一个分区数值不重叠,使用PARTITION BY LIST
、VALUES IN
关键字。跟Range
分区相似,不使用COLUMNS
关键字时List
括号内必须为整数字段名或返回肯定整数的函数。
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT,
store_id INT
)
PARTITION BY LIST(store_id) (
PARTITION pNorth VALUES IN (3,5,6,9,17),
PARTITION pEast VALUES IN (1,2,10,11,19,20),
PARTITION pWest VALUES IN (4,12,13,14,18),
PARTITION pCentral VALUES IN (7,8,15,16)
);
数值必须被全部分区覆盖,不然插入一个不属于任何一个分区的数值会报错。
mysql> CREATE TABLE h2 (
-> c1 INT,
-> c2 INT
-> )
-> PARTITION BY LIST(c1) (
-> PARTITION p0 VALUES IN (1, 4, 7),
-> PARTITION p1 VALUES IN (2, 5, 8)
-> );
Query OK, 0 rows affected (0.11 sec)
mysql> INSERT INTO h2 VALUES (3, 5);
ERROR 1525 (HY000): Table has no partition for value 3
当插入多条数据出错时,若是表的引擎支持事务(Innodb
),则不会插入任何数据;若是不支持事务,则出错前的数据会插入,后面的不会执行。
可使用IGNORE
关键字忽略出错的数据,这样其余符合条件的数据会所有插入不受影响。
mysql> TRUNCATE h2;
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM h2;
Empty set (0.00 sec)
mysql> INSERT IGNORE INTO h2 VALUES (2, 5), (6, 10), (7, 5), (3, 1), (1, 9);
Query OK, 3 rows affected (0.00 sec)
Records: 5 Duplicates: 2 Warnings: 0
mysql> SELECT * FROM h2;
+------+------+
| c1 | c2 |
+------+------+
| 7 | 5 |
| 1 | 9 |
| 2 | 5 |
+------+------+
3 rows in set (0.00 sec)
与Range
分区相同,添加COLUMNS
关键字可支持非整数和多列。
Hash
分区主要用来确保数据在预先肯定数目的分区中平均分布,Hash
括号内只能是整数列或返回肯定整数的函数,实际上就是使用返回的整数对分区数取模。
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT,
store_id INT
)
PARTITION BY HASH(store_id)
PARTITIONS 4;
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT,
store_id INT
)
PARTITION BY HASH( YEAR(hired) )
PARTITIONS 4;
Hash
分区也存在与传统Hash
分表同样的问题,可扩展性差。MySQL
也提供了一个相似于一致Hash
的分区方法-线性Hash
分区,只须要在定义分区时添加LINEAR
关键字,若是对实现原理感兴趣,能够查看官方文档。
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT,
store_id INT
)
PARTITION BY LINEAR HASH( YEAR(hired) )
PARTITIONS 4;
按照KEY进行分区相似于按照HASH分区,除了HASH分区使用的用户定义的表达式,而KEY分区的 哈希函数是由MySQL 服务器提供。MySQL 簇(Cluster)使用函数MD5()来实现KEY分区;对于使用其余存储引擎的表,服务器使用其本身内部的 哈希函数,这些函数是基于与PASSWORD()同样的运算法则。
Key
分区与Hash
分区很类似,只是Hash
函数不一样,定义时把Hash
关键字替换成Key
便可,一样Key
分区也有对应与线性Hash
的线性Key
分区方法。
CREATE TABLE tk (
col1 INT NOT NULL,
col2 CHAR(5),
col3 DATE
)
PARTITION BY LINEAR KEY (col1)
PARTITIONS 3;
另外,当表存在主键或惟一索引时可省略Key
括号内的列名,Mysql
将按照主键-惟一索引的顺序选择,当找不到惟一索引时报错。
子分区是分区表中每一个分区的再次分割。建立子分区方法:
CREATE TABLE ts (id INT, purchased DATE)
PARTITION BY RANGE( YEAR(purchased) )
SUBPARTITION BY HASH( TO_DAYS(purchased) )
SUBPARTITIONS 2 (
PARTITION p0 VALUES LESS THAN (1990),
PARTITION p1 VALUES LESS THAN (2000),
PARTITION p2 VALUES LESS THAN MAXVALUE
);
和
CREATE TABLE ts (id INT, purchased DATE)
PARTITION BY RANGE( YEAR(purchased) )
SUBPARTITION BY HASH( TO_DAYS(purchased) ) (
PARTITION p0 VALUES LESS THAN (1990) (
SUBPARTITION s0
DATA DIRECTORY = '/disk0/data'
INDEX DIRECTORY = '/disk0/idx',
SUBPARTITION s1
DATA DIRECTORY = '/disk1/data'
INDEX DIRECTORY = '/disk1/idx'
),
PARTITION p1 VALUES LESS THAN (2000) (
SUBPARTITION s2
DATA DIRECTORY = '/disk2/data'
INDEX DIRECTORY = '/disk2/idx',
SUBPARTITION s3
DATA DIRECTORY = '/disk3/data'
INDEX DIRECTORY = '/disk3/idx'
),
PARTITION p2 VALUES LESS THAN MAXVALUE (
SUBPARTITION s4
DATA DIRECTORY = '/disk4/data'
INDEX DIRECTORY = '/disk4/idx',
SUBPARTITION s5
DATA DIRECTORY = '/disk5/data'
INDEX DIRECTORY = '/disk5/idx'
)
);
须要注意的是:每一个分区的子分区数必须相同。若是在一个分区表上的任何分区上使用SUBPARTITION
来明肯定义任何子分区,那么就必须定义全部的子分区,且必须指定一个全表惟一的名字。
对现有表分区的原则与传统分表同样。
传统的按照增量区间分表对应于分区的Range
分区,好比对表的访问可能是近期产生的新数据,历史数据访问较少,则能够按必定时间段(好比年或月)或必定数量(好比100万)对表分区,具体根据哪一种取决于表索引结构。分区后最后一个分区即为近期产生的数据,当一段时间事后数据量再次变大,可对最后一个分区从新分区(REORGANIZE PARTITION
)把一段时间(一年或一月)或必定数量(好比100万)的数据分离出去。
传统的散列方法分表对应于分区的Hash/Key分区,具体方法上面已经介绍过。
分区的目的是为了提升查询效率,若是查询范围是全部分区那么就说明分区没有起到做用,咱们用explain partitions
命令来查看SQL
对于分区的使用状况。
通常来讲,就是在where
条件中加入分区列。
好比表salaries
结构为:
mysql> show create table salaries\G;
*************************** 1. row ***************************
Table: salaries
Create Table: CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/*!50100 PARTITION BY RANGE (year(from_date))
(PARTITION p1 VALUES LESS THAN (1985) ENGINE = InnoDB,
PARTITION p2 VALUES LESS THAN (1986) ENGINE = InnoDB,
PARTITION p3 VALUES LESS THAN (1987) ENGINE = InnoDB,
PARTITION p4 VALUES LESS THAN (1988) ENGINE = InnoDB,
PARTITION p5 VALUES LESS THAN (1989) ENGINE = InnoDB,
PARTITION p6 VALUES LESS THAN (1990) ENGINE = InnoDB,
PARTITION p7 VALUES LESS THAN (1991) ENGINE = InnoDB,
PARTITION p8 VALUES LESS THAN (1992) ENGINE = InnoDB,
PARTITION p9 VALUES LESS THAN (1993) ENGINE = InnoDB,
PARTITION p10 VALUES LESS THAN (1994) ENGINE = InnoDB,
PARTITION p11 VALUES LESS THAN (1995) ENGINE = InnoDB,
PARTITION p12 VALUES LESS THAN (1996) ENGINE = InnoDB,
PARTITION p13 VALUES LESS THAN (1997) ENGINE = InnoDB,
PARTITION p14 VALUES LESS THAN (1998) ENGINE = InnoDB,
PARTITION p15 VALUES LESS THAN (1999) ENGINE = InnoDB,
PARTITION p16 VALUES LESS THAN (2000) ENGINE = InnoDB,
PARTITION p17 VALUES LESS THAN (2001) ENGINE = InnoDB,
PARTITION p18 VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */
则下面的查询没有利用分区,由于partitions
中包含了全部的分区:
mysql> explain partitions select * from salaries where salary > 100000\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: salaries
partitions: p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16,p17,p18
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 2835486
Extra: Using where
只有在where
条件中加入分区列才能起到做用,过滤掉不须要的分区:
mysql> explain partitions select * from salaries where salary > 100000 and from_date > '1998-01-01'\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: salaries
partitions: p15,p16,p17,p18
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1152556
Extra: Using where
与普通搜索同样,在运算符左侧使用函数将使分区过滤失效,即便与分区函数想同也同样:
mysql> explain partitions select * from salaries where salary > 100000 and year(from_date) > 1998\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: salaries
partitions: p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16,p17,p18
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 2835486
Extra: Using where
count
、sum
等统计操做只能对全部切分表进行操做后以后在应用层再次计算得出最后统计数据。而分区表则不受影响,可直接统计。Queries involving aggregate functions such as SUM() and COUNT() can easily be parallelized. A simple example of such a query might be SELECT salesperson_id, COUNT(orders) as order_total FROM sales GROUP BY salesperson_id;. By “parallelized,” we mean that the query can be run simultaneously on each partition, and the final result obtained merely by summing the results obtained for all partitions.
分区对原系统改动最小,分区只涉及数据库层面,应用层不须要作出改动。
分区有个限制是主表的全部惟一字段(包括主键)必须包含分区字段,而分表没有这个限制。
分表包括垂直切分和水平切分,而分区只能起到水平切分的做用。
tddl主要分为三次,matrix、group、atom层;
读写分离、权重、写的HA切换、读的HA切换、slave节点
1 单个数据库的抽象
2 jboss数据源, ip port 用户名密码均可 以动态修改
3 Thread count模式,保护 业务的处理线程,超过指定值,保护启动。
4 动态阻止某个SQL执行
5 执行次数统计和限制
目前基于tddl进行分库分表后,本来一个数据库上的自增id的结果,在分库分表下并非全局惟一的. 因此,分库分表后须要有一种技术能够生成全局的惟一id.
惟一键的生成方式必须具有:1)全局惟一;2)高可用;3)高性能;
tddl主要使用数据库+内存的方式实现,在内存中进行分配 优点:简单高效 缺点:没法保证自增顺序以下,下面内步长1000:
group | value |
group_0 | 0 |
group_1 | 1000 |
group_2 | 2000 |
group_3 | 3000 |
当须要产生惟一键时,从上面4个group中随机选择一个,获取value+步长的id,例如,从group_1获取1000~1000+1000的id,批量获取,提升性能。获取以后,数据库的记录变为下面的格式:
group | value |
group_0 | 0 |
group_1 | 5000 |
group_2 | 2000 |
group_3 | 3000 |
每次获取以后,将对应group的值变为,value+group的个数*步长。