MySQL深入了解与性能优化

 

  • 索引优化

索引创建

CREATE [UNIQUE] INDEX indexName ON mytable(columnname(length));

ALTER mytable ADD [UNIQUE] INDEX [indexName] ON (columnname(length));

索引删除

DROP INDEX [indexName]  ON mytable;

查看索引

SHOW INDEX FROM mytable;

优势:

1.类似图书馆建书目索引,提高数据检索的效率,降低数据库的IO成本

2.通过索引对数据进行排序,降低数据库的排序成本,降低了CPU的消耗

劣势:

  1. 实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引也是占用空间的。
  2. 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT,UPDATE,DELETE.因为更新表时,MYSQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新带来的键值变化后的索引信息。
  3. 索引只是提高效率的一个因素,如果你的MYSQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询。

哪些情况需要创建索引:

1.主键自动建立唯一索引

2.频繁作为查询条件的字段应该创建索引

3.查询中与其它表关联的字段,外键关系建立索引

4.频繁更新的字段不适合建索引

5.where条件里用不到的的字段不创建索引

6.单键/组合索引的选择问题(在高并发的下倾向创建组合索引)

7.查询中排序的字段,排序的字段若通过索引区访问,将大大提高排序速度

8.查询中统计或者分组的字段

哪些情况不需要建索引:

1.表的记录太少

2.经常增删改的字段

3.数据重复且分布平均的表字段

多表连接—Join 语句优化:

1.尽可能减少join 语句中的NestedLoop的循环总次数,永远用小结果集驱动大结果集。

2.优先优化NestedLoop的内存循环。

3.保证Join语句中被驱动表上的Join条件字段已经被索引。

4. 当无法保证被驱动表的Join条件字段被索引,且内存充足的情况下适当加大JoinBuffer大小的设置。

建索引的建议:

  1. 对于单键索引,尽量选择针对当前query过滤性更好的索引;
  2. 在选择组合索引的时候,当前的query中过滤性最好的字段在索引的字段顺序中位置越靠前越好;
  3. 在选择组合索引的时候,尽量选择可以能够包含当前query中where子句中更多字段的索引;
  4. 尽可能通过分析统计信息和调整query的写法来达到选择合适的索引的目的。

Order by优化:

Mysql支持两种方式的排序:FileSort和Index,Index效率高。Sql扫描索引本身完成排序,FileSort效率较低。

1.Order by子句,尽量使用index方式排序,避免使用fileSort方式排序

2.尽可能在索引列上完成排序操作,遵循索引建的最佳左前缀

3.如果不在索引列上,filesort有两种算法:双路排序和单路排序

双路排序:

Mysql 4.1 之前使用的是双路排序,字面意思是进行两次扫描磁盘,最终得到数据。

读取行指针和orderby列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出。

从磁盘取排序字段,在buffer进行排序,再从磁盘取其它字段。

单路排序:

Mysql 4.1之后,出现了第二种改进算法, 就是单路排序。

从磁盘读取查询需要的所有的列,按照order by列在buffer对他们进行排序,然后扫描排序后的列表进行输出,它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是他会使用更多的磁盘空间,因为他把每一行都保存在内存中了。

注意:

因为单路排序是一次把所有字段取出,所以可能取出数据的总大小超出sort_buffer的容量,导致没次只能取出sort_buffer容量大小的数据, 进行排序(创建tmp文件多路合并),拍完序再取出sort_buffer容量大小,再排, 从而导致多次IO。

优化策略:

增大sort_buffer_size参数的设置

增大max_length_for_sort_data参数的设置

增高oredr by的速度:

  1. Order by 时不要使用select * ,只select出需要的字段,在这里的影响是:
    1. 当Query的字段的大小总和小于max_length_for_sort_data而且排序字段不是TEXT/BLOB类型时,会用改进后的算法-单路排序,否则用老算法-多路排序。
    2. 两种算法的数据都有可能超出sort_buffer的容量,超出之后,会创建tmp文件进行合并排序,会导致多次IO,但是用单路排序算法的风险更大一些,所以要提高sort_buffer_size。
  2. 尝试提高sort_buffer_size

 不管用哪种算法,提高这个参数都会提高效率,当然要根据系统的能力去提高,因为这个参数是针对每个进程的。

  1. 尝试提高max_length_for_sort_data

提高这个参数,会增加使用改进算法的概率。如果没有设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状的磁盘IO活动和低的处理器使用率。

为排序使用索引

Mysql两种排序方式:文件排序或扫描有序索引排序

Mysql能为排序与查询使用相同的索引

Group by关键字的优化:

group by分组之前必排序。

  1. Group by的实质是先排序,后进行分组,按照索引建的最佳左前缀
  2. 当无法使用索引列,增大sort_buffer_size、max_length_for_sort_data参数的设置
  3. Where高于having,能写在where子句下的限定条件就不要写在having下了。

为什么用B/B+树(平衡多路查找树)这种结构来实现索引呢??

系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来

局部性原理与磁盘预读:

实际过程中,磁盘并不是每次严格按需读取,而是每次都会预读。磁盘读取完需要的数据后,会按顺序再多读一部分数据到内存中,这样做的理论依据是计算机科学中注明的局部性原理:

当一个数据被用到时,其附近的数据也通常会马上被使用

程序运行期间所需要的数据通常比较集中

mysql是基于磁盘的数据库,索引是以索引文件的形式存在于磁盘中的,索引的查找过程就会涉及到磁盘IO消耗,磁盘IO的消耗相比较于内存IO的消耗要高好几个数量级,所以索引的组织结构要设计得在查找关键字时要尽量减少磁盘IO的次数。

(1)由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间),

因此对于具有局部性的程序来说,预读可以提高I/O效率.预读的长度一般为页(page)的整倍数。

(2)MySQL(默认使用InnoDB引擎),将记录按照页的方式进行管理,每页大小默认为16K(这个值可以修改)。linux 默认页大小为4K。

B-Tree借助计算机磁盘预读的机制,并使用如下技巧:
    每次新建节点时,直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里,加之计算机存储分配都是按页对齐的,就实现了一个结点只需一次I/O。

假设 B-Tree 的高度为 h, B-Tree中一次检索最多需要h-1次I/O(根节点常驻内存),渐进复杂度为O(h)=O(logdN)O(h)=O(logdN)。

一般实际应用中,出度d是非常大的数字,通常超过100,因此h非常小(通常不超过3,也即索引的B+树层次一般不超过三层,所以查找效率很高)。

一棵高度为2的B+树,大概可以存放:2w大小为1k的记录

一个高度为3的B+树大概可以存放:2000w大小为1k的记录

为什么mysql的索引使用B+树而不是B树呢??
(1)B+树更适合外部存储(一般指磁盘存储),由于内节点(非叶子节点)不存储data,所以一个节点可以存储更多的内节点,每个节点能索引的范围更大更精确。也就是说使用B+树单次磁盘IO的信息量相比较B树更大,IO效率更高。
(2)mysql是关系型数据库,经常会按照区间来访问某个索引列,B+树的叶子节点间按顺序建立了链指针,加强了区间访问性,所以B+树对索引列上的区间范围查询很友好。而B树每个节点的key和data在一起,无法进行区间查找。

Explain

–建表

CREATE TABLE `emp` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `dept_id` int(11),

  `name` varchar(30),

  `sex` char,

   PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

 

CREATE TABLE `dept` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `dept_name` varchar(30),

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

 

CREATE TABLE `dept_location` (

  `dept_id` int(11) NOT NULL AUTO_INCREMENT,

  `location` varchar(30),

PRIMARY KEY (`dept_id`)

) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

 

CREATE TABLE `adress` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `emp_id` int(11),

  `address` varchar(30),

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

 

insert into emp(dept_id,name,sex) values(1,’Jane’,’0′);

insert into emp(dept_id,name,sex) values(2,’Mary’,’0′);

insert into emp(dept_id,name,sex) values(1,’Jack’,’1′);

insert into emp(dept_id,name,sex) values(3,’Leo’,’0′);

insert into emp(dept_id,name,sex) values(1,’Ruby’,’0′);

 

insert into dept(dept_name) values(‘Business’);

insert into dept(dept_name) values(‘Market’);

insert into dept(dept_name) values(‘Software’);

 

insert into adress(emp_id,address) values(1,’深圳’);

insert into adress(emp_id,address) values(2,’深圳’);

insert into adress(emp_id,address) values(3,’深圳’);

insert into adress(emp_id,address) values(4,’北京’);

insert into adress(emp_id,address) values(5,’北京’);

 

Select_type:

explain select emp.name,(select dept.dept_name from dept where dept.id = emp.dept_id) from emp;

 

 

explain select d1.name,(select id from adress) d2  

from  (select id,name from emp where sex =’0′) d1

where d1.name  =’Mary’

union  

(select id,dept_name from dept);

 

Type:

explain select * from emp;

explain select * from emp where id = 1;

 

–eq_ref

explain select * from dept,dept_location where dept.id = dept_location.dept_id;

 

–ref  非唯一性索引扫描

create index idx_emp_name on emp(name);

explain select * from emp where name =’Mary’;

 

–index

explain select id from emp;

 

–possible_keys key

 

  1.  

explain select dept.*

from emp,dept,dept_location

where emp.id = dept.id and dept.id = dept_location.dept_id

and dept.dept_name = ‘Business’;

 

  1.  覆盖索引

create index idx_emp_ns on emp(name,sex);

explain select name ,sex from emp;

 

— key_len

列类型

KEY_LEN

备注

id int

key_len = 4+1

int为4bytes,允许为NULL,加1byte

id bigint not null

key_len=8

bigint为8bytes

user char(30) utf8

key_len=30*3+1

utf8每个字符为3bytes,允许为NULL,加1byte

user varchar(30) not null utf8

key_len=30*3+2

utf8每个字符为3bytes,变长数据类型,加2bytes

user varchar(30) utf8

key_len=30*3+2+1

utf8每个字符为3bytes,允许为NULL,加1byte,变长数据类型,加2bytes

detail text(10) utf8

key_len=30*3+2+1

TEXT截取部分,被视为动态列类型。

说明:索引字段的附加信息:可以分为变长和定长数据类型讨论,当索引字段为定长数据类型时,如char,int,datetime,需要有是否为空的标记,这个标记占用1个字节(对于not null的字段来说,则不需要这1字节);对于变长数据类型,比如varchar,除了是否为空的标记外,还需要有长度信息,需要占用两个字节。

对于,char、varchar、blob、text等字符集来说,key len的长度还和字符集有关,latin1一个字符占用1个字节,gbk一个字符占用2个字节,utf8一个字符占用3个字节。

character set:utf8=3,gbk=2,latin1=1  len = 3

key_len的长度计算公式:

varchr(10)变长字段且允许NULL  =  10 * 3 + 1(NULL) + 2(变长字段)

varchr(10)变长字段且不允许NULL =  10 * 3 + 2(变长字段)

char(10)固定字段且允许NULL   =  10 * 3 + 1(NULL)

char(10)固定字段且不允许NULL =  10 * 3

 

Explain select * from dept , dept_location

where dept.id = dept_location.dept_id

and dept_location.location =’ac’;

 

–建索引优化

 

create index idx_dl_location on dept_location(location);

Explain select * from dept , dept_location

where dept.id = dept_location.dept_id

and dept_location.location =’ac’;

 

Extra:

CREATE TABLE `t1` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `col2` int(11),

  `col3` varchar(30),

  `col4` char,

  `col5` varchar(30),

   PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

 

create index idx_t1_234 on t1(col2,col3,col4);

–using filesort

explain select col2 from t1 where col2 =5 order by col4;

 

–优化

explain select col2 from t1 where col2 =5 order by col3,col4;

–using temporary

explain select col3 from t1 where col2>2 group by col3 \G;

explain select col3 from t1 where col2>2 group by col2,col3 \G;

慢查询日志:

Mysql提供的一种日志记录,它用来记录mysql中响应时间超过阈值(long_query_time)的语句.

long_query_time的默认值为10, 即记录运行10s以上的语句。

开启MySQL的慢查询日志功能
默认情况下,MySQL是没有开启慢查询日志的。要开启这个功能,

Show variables like ‘%slow_query_log%’;

 

默认10s

开启:

Set global slow_query_log = 1

只对当前的数据库生效,如果mysql重启之后会失效。

show global status like %Slow_queries%;   查询当前系统有哪些慢查询sql

如果要永久生效:

我们需要修改MySQL的配置文件,windows下修改my.ini,Linux下修改my.cnf文件,在[mysqld]最后增加如下命令:

slow_query_log
long_query_time = 1

slow_query_log_file = filepath

最后一个参数指定慢查询日志的文件存放路径,没有指定的话,系统默认会给一个缺省的文件:
host_name-slow.log

*如果不是调优需要,一般不建议启动该参数,因为开启慢查询日志或多或少会带来一定的性能影响*

Mysql提供的日志分析工具:mysqldumpslow

 

如:mysqldumpslow -s r -t 10 /var/lib/,ysql/slow.log

 

Show Profile:

是mysql提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于sql的调优的测量。

默认情况下参数处于关闭状态,并保存最近15次的运行结果。

https://dev.mysql.com/doc/refman/5.5/en/show-profile.html

  show variables like ‘%profiling%’;

set profiling = on;

  运行sql

开启之后,查询的sql就会被记录;

查看结果

 Show profiles;

      诊断sql:

     Show profile cpu, block io for query <查询的id>;

 

比较耗内存/时间的四个操作:

Converting HEAP to MyISAM 查询结果太大, 内存都不够用了往磁盘上搬。

Creating tmp table创建临时表

Coping to tmp table on disc 把内存中临时表复制到磁盘

locked

全局查询日志:
   set global general_log = 1;

   set global log_output = table;

此后,查询的sql都会记录在mysql数据库里的general_log表,可以用下面的命令查看:

Select * from mysql.general_log;

 

**永远不要在生产环境开启这个功能。

二、InnoDB锁

InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁。

数据库事务隔离级别:

在并发事务处理带来的问题中,“更新丢失”通常应该是完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。

“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。数据库实现事务隔离的方式,基本可以分为以下两种。

  • READ UNCOMMITTED(未提交读)

在这种隔离级别下,事物中的修改,即使没有提交,对其他失误也都是可见的。

事务可以读取未提交的数据,这也被称为脏读(Dirty Read)。一般在实际应用中很少使用。

  • READ COMMITTED(提交读)

一个事务开始时,只能“看见”已经提交的事务所做的修改。换句话说,一个事务从开始到提交之前,所做的任何修改对其它事务都是不可见的。这个级别也叫做不可重复读,因为在一个事务中两次执行同样的查询,可能会得到不一样的结果。 大多数数据库系统默认的隔离级别都是READ COMMITTED(但mysql不是)。

  • REPEATABLE READ(可重复读)

解决了脏读和不可重复读的问题,保证了在同一个事务当中多次读取同样的记录的结果是一致的。但是可能存在幻读的问题。InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC)解决了幻读的问题。可重复读是mysql的默认事务隔离级别。

  • SERIALIZATION(可串行化)

是最高的隔离级别。它通过强制事务串行执行,避免了前面说的幻读的问题。SERIALIZATION会在读取的每一行的数据上都加上锁,所以可能导致大量的超时和锁争用的问题。实际应用中也很少用到这个隔离级别,只要在非常需要保证数据一致性的情况而且接收没有并发的情况下,才考虑该级别。

数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上 “串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏 感,可能更关心数据并发访问的能力。

为了解决“隔离”与“并发”的矛盾,ISO/ANSI SQL92定义了4个事务隔离级别,每个级别的隔离程度不同,允许出现的副作用也不同,应用可以根据自己的业务逻辑要求,通过选择不同的隔离级别来平衡 “隔离”与“并发”的矛盾。下表很好地概括了这4个隔离级别的特性。 

 

InnoDB的行锁模式及加锁方法

— 加锁

lock table mylock read, class write;

— 解锁

unlock tables;

读锁: 当前session对锁定表的查询操作可以执行,对其它表的任何操作都不可执行

        其它session对当前表的查询操作可以执行,对当前表的更新操作不可以执行

写锁:当前session对锁定表的查询+更新+插入操作都可以执行,对其它表的任何操作都不可执行;

       其它session对锁定表的查询被阻塞,需要等待锁被释放。

总结: 读锁会阻塞写, 但是不会阻塞读。而写锁则会把读和写都堵塞。

 

间隙锁:

当我们使用范围条件而不是相等条件检索数据,并请求共享锁或排他锁时,InnoDB 会给复合条件的已有数据记录的索引项加锁;对于在条件范围内但是不存在的记录叫做“间隙(GAP)”,

InnoDB也会给这个“间隙”加锁, 这种锁机制就是所谓的间隙锁(Next-Key锁)。

 

影响:

因为Query执行过程中,通过范围查找的话,他会锁定整个范围内所有的索引键值,即使这个键不存在。在锁定的过程中,无法往锁定范围内插入任何数据。

GAP锁,就是RR隔离级别,相对于RC隔离级别,不会出现幻读的关键。确实,GAP锁锁住的位置,也不是记录本身,而是两条记录之间的GAP。所谓幻读,就是同一个事务,连续做两次当前读 (例如:select * from t1 where id = 10 for update;),那么这两次当前读返回的是完全相同的记录 (记录数量一致,记录本身也一致),第二次的当前读,不会比第一次返回更多的记录 (幻象)。

如何保证两次当前读返回一致的记录,那就需要在第一次当前读与第二次当前读之间,其他的事务不会插入新的满足条件的记录并提交。为了实现这个功能,GAP锁应运而生。

如何分析行锁定:

通过检查InnoDb_row_lock状态变量来分析系统上的行锁的争夺情况。

Show status like ‘Innodb_row_lock%’;

 

Innodb_row_lock_current_waits: 当前正在等待锁定的数量;

Innodb_row_lock_time: 从系统启动到现在, 锁定时间的总长度;

Innodb_row_lock_time_avg: 每次等待所花的平均时间;

Innodb_row_lock_time_max:从系统启动到现在等待最长的一次所花的时间;

Innodb_row_lock_waits:从系统启动到现在总共等待的次数。

 

优化建议:

  • 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁;
  • 合理设计索引,尽量缩小锁的范围
  • 尽可能较少的索引条件,避免间隙锁
  • 尽量控制事务大小,减少锁定资源量和时间长度
  • 尽可能降低事务的隔离级别

CREATE TABLE `mylock` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `name` varchar(30),

   PRIMARY KEY (`id`)

) ENGINE=MyISAM;

insert into mylock(name) values(‘a’);

insert into mylock(name) values(‘b’);

insert into mylock(name) values(‘c’);

insert into mylock(name) values(‘d’);

insert into mylock(name) values(‘e’);

insert into mylock(name) values(‘f’);

insert into mylock(name) values(‘g’);

 

–读锁

lock table mylock read;

select * from mylock;

Session 1

update mylock set name =’s’ where id=1;

Session 2    –读阻塞写

update mylock set name =’s’ where id=1;

 

Session1释放读锁,session2写成功

 

–写锁:

 

间隙锁:

CREATE TABLE `innodb_lock` (

  `id` int(11) NOT NULL,

  `name` varchar(30),

   PRIMARY KEY (`id`)

) ENGINE=INNODB;

insert into innodb_lock values(1,’a’);

insert into innodb_lock values(3,’b’);

insert into innodb_lock values(4,’c’);

insert into innodb_lock values(5,’d’);

insert into innodb_lock values(6,’e’);

insert into innodb_lock values(7,’f’);

insert into innodb_lock values(8,’g’);

–session 1

Set auto_commit = 0;

update innodb_lock set name =’update’ where id>1 and id<4;

–session 2

insert into innodb_lock values(2,’b’);
–session 1

Commit;

Id为2 的记录虽然不存在,但是这行间隙也会被锁住。

三、主从复制:

  1. master将改变记录到二进制日志(binary log)。这些记录过程叫做二进制日志事件,binary log events;
  2. Slave将binary log events拷贝到它的中继日志(relay log);
  3. Slave重做中继日志中的事件,将改变应用到自己的数据库中,MYSQL的复制是异步的且串行化的。

主从复制解决的问题:

  1. 数据分布

    可以随意的停止或开始复制,并在不同的地理位置来分布数据备份,例如不同的数据中心。

  1. 负载均衡
        可以将读操作分布到多个服务器上,实现对读密集型应用的优化。(DNS轮询,网络负载均衡…)
  2. 备份
  3. 高可用和故障切换
         能够帮助应用程序避免MYSQL单点失败,发生故障时可以通过切换数据库缩短宕机时间。
  4. Mysql升级测试
         使用一个更高版本的MYSQL作为备库,保证在升级全部实例前,查询能够在备库按照预期执行。

原理:

基于语句的复制:

在mysql 5.0及之前的版本中只支持基于语句的复制(也称为逻辑复制),基于语句复制的模式下,主库会记录那些造成数据更改的查询,当备库读取到并重放这些事件的时候,实际上是把主库上执行过得sql再执行一遍。

基于行的复制

mysql 5.1开始支持基于行的复制,这种方式会将实际数据记录在二进制日志中。

复制的基本原则:

每个Slave只有一个master;

每个Slave只能有一个唯一的服务器id;

每个master可以有多个Slave。

一主一从最常见。

主从复制配置过程:

主节点:

  1. 启用二进制日志。
  2. 为当前节点设置一个全局唯一的server_id。
  3. 创建有复制权限的用户账号 REPLIACTION SLAVE ,REPLIATION CLIENT。

从节点:

  1. 启动中继日志。
  2. 为当前节点设置一个全局唯一的server_id。
  3. 使用有复制权限的用户账号连接至主节点,并启动复制线程。

分库、分表:

当用户量级上来后,写请求越来越多,该怎么办?加一个Master是不能解决问题的, 因为数据要保存一致性,写操作需要2个master之间同步,相当于是重复了,而且更加复杂。

这时就需要用到分库分表(sharding),对写操作进行切分。

用户请求量太大

因为单服务器TPS,内存,IO都是有限的。

解决方法:分散请求到多个服务器上; 其实用户请求和执行一个sql查询是本质是一样的,都是请求一个资源,只是用户请求还会经过网关,路由,http服务器等。

单库太大

单个数据库处理能力有限;单库所在服务器上磁盘空间不足;

单库上操作的IO瓶颈 解决方法:切分成更多更小的库

单表太大

CRUD都成问题;索引膨胀,查询超时

解决方法:切分成多个数据集更小的表。

分库分表的方式方法:

一般就是垂直切分和水平切分,这是一种结果集描述的切分方式,是物理空间上的切分。

然后是单个库太大,这时我们要看是因为表多而导致数据多,还是因为单张表里面的数据多。

分库:如果是因为表多而数据多,使用垂直切分,根据业务切分成不同的库。

分表: 如果是因为单张表的数据量太大,这时要用水平切分,即把表的数据按某种规则切分成多张表,甚至多个库上的多张表。 

分库分表的顺序应该是先垂直分,后水平分

垂直拆分

垂直分表

也就是“大表拆小表”,基于列字段进行的。一般是表中的字段较多,将不常用的, 数据较大,长度较长(比如text类型字段)的拆分到“扩展表“。一般是针对那种几百列的大表,也避免查询时,数据量太大造成的“跨页”问题。

垂直分库

垂直分库针对的是一个系统中的不同业务进行拆分,比如用户User一个库,商品Producet一个库,订单Order一个库。 切分后,要放在多个服务器上,而不是一个服务器上。为什么? 我们想象一下,一个购物网站对外提供服务,会有用户,商品,订单等的CRUD。没拆分之前, 全部都是落到单一的库上的,这会让数据库的单库处理能力成为瓶颈。按垂直分库后,如果还是放在一个数据库服务器上, 随着用户量增大,这会让单个数据库的处理能力成为瓶颈,还有单个服务器的磁盘空间,内存,tps等非常吃紧。 所以我们要拆分到多个服务器上,这样上面的问题都解决了,以后也不会面对单机资源问题。

数据库业务层面的拆分,和服务的“治理”,“降级”机制类似,也能对不同业务的数据分别的进行管理,维护,监控,扩展等。 数据库往往最容易成为应用系统的瓶颈,而数据库本身属于“有状态”的,相对于Web和应用服务器来讲,是比较难实现“横向扩展”的。 数据库的连接资源比较宝贵且单机处理能力也有限,在高并发场景下,垂直分库一定程度上能够突破IO、连接数及单机硬件资源的瓶颈。

水平拆分

水平分表

针对数据量巨大的单张表(比如订单表),按照某种规则(RANGE,HASH取模等),切分到多张表里面去。 但是这些表还是在同一个库中,所以库级别的数据库操作还是有IO瓶颈。不建议采用。

水平分库分表

将单张表的数据切分到多个服务器上去,每个服务器具有相应的库与表,只是表中数据集合不同。 水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈。

水平分库分表切分规则

  1. RANGE

从0到10000一个表,10001到20000一个表;

  1. HASH取模

一个商场系统,一般都是将用户,订单作为主表,然后将和它们相关的作为附表,这样不会造成跨库事务之类的问题。 取用户id,然后hash取模,分配到不同的数据库上。

  1. 地理区域

比如按照华东,华南,华北这样来区分业务,七牛云应该就是如此。

  1. 时间

按照时间切分,就是将6个月前,甚至一年前的数据切出去放到另外的一张表,因为随着时间流逝,这些表的数据 被查询的概率变小,所以没必要和“热数据”放在一起,这个也是“冷热数据分离”。

https://www.cnblogs.com/mfmdaoyou/p/7246711.html

    原文作者:mysql
    原文地址: https://blog.csdn.net/suchahaerkang/article/details/84031112
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞