mysql 超大数据/表管理技巧

  如果你对长篇大论没有兴趣,也可以直接看看结果,或许你对结果感兴趣。在实际应用中经过存储、优化可以做到在超过9千万数据中的查询响应速度控制在1到20毫秒。看上去是个不错的成绩,不过优化这条路没有终点,当我们的系统有超过几百人、上千人同时使用时,仍然会显的力不从心。

  目录:

  分区存储

  优化查询

  改进分区

  模糊搜索

  持续改进的方案

  正文:

  分区存储

  对于超大的数据来说,分区存储是一个不错的选择,或者说这是一个必选项。对于本例来说,数据记录来源不同,首先可以根据来源来划分这些数据。但是仅仅这样还不够,因为每个来源的分区的数据都可能超过千万。这对数据的存储和查询还是太大了。MySQL5.x以后已经比较好的支持了数据分区以及子分区。因此数据就采用分区+子分区来存储。

  下面是基本的数据结构定义:

  复制代码 代码如下:

  CREATE TABLE `tmp_sampledata` (

  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,

  `username` varchar(32) DEFAULT NULL,

  `passwd` varchar(32) DEFAULT NULL,

  `email` varchar(64) DEFAULT NULL,

  `nickname` varchar(32) DEFAULT NULL,

  `siteid` varchar(32) DEFAULT NULL,

  `src` smallint(6) NOT NULL DEFAULT ‘0′,

  PRIMARY KEY (`id`,`src`)

  ) ENGINE=MyISAM AUTO_INCREMENT=95660181 DEFAULT CHARSET=gbk

  /*!50500 PARTITION BY LIST COLUMNS(src)

  SUBPARTITION BY HASH (id)

  SUBPARTITIONS 5

  (PARTITION pose VALUES IN (1) ENGINE = MyISAM,

  PARTITION p2736 VALUES IN (2) ENGINE = MyISAM,

  PARTITION p736736 VALUES IN (3) ENGINE = MyISAM,

  PARTITION p3838648 VALUES IN (4) ENGINE = MyISAM,

  PARTITION p842692 VALUES IN (5) ENGINE = MyISAM,

  PARTITION p7575 VALUES IN (6) ENGINE = MyISAM,

  PARTITION p386386 VALUES IN (7) ENGINE = MyISAM,

  PARTITION p62678 VALUES IN (8) ENGINE = MyISAM) */

  对于拥有分区及子分区的数据表,分区条件(包括子分区条件)中使用的数据列,都应该定义在primary key 或者 unique key中。详细的分区定义格式,可以参考MySQL的文档。上面的结构是第一稿的存储方式(后文还将进行修改)。采用load data infile的方式加载,用时30分钟加载8千万记录。感觉还是挺快的(bulk_insert_buffer_size=8m)。

  基本查询优化

  数据装载完毕后,我们测试了一个查询:

  复制代码 代码如下:

  mysql explain select * from tmp_sampledata where id=9562468\G

  *************************** 1. row ***************************

  id: 1

  select_type: SIMPLE

  table: tmp_sampledata

  type: ref

  possible_keys: PRIMARY

  key: PRIMARY

  key_len: 8

  ref: const

  rows: 8

  Extra:

  1 row in set (0.00 sec)

  这是毋庸置疑的,通过id进行查询是使用了主键,查询速度会很快。但是这样的做法几乎没有意义。因为对于终端用户来说,不可能知晓任何的资料的id的。假如需要按照username来进行查询的话:

  复制代码 代码如下:

  mysql explain select * from tmp_sampledata where username = ‘yourusername‘\G

  *************************** 1. row ***************************

  id: 1

  select_type: SIMPLE

  table: tmp_sampledata

  type: ALL

  possible_keys: NULL

  key: NULL

  key_len: NULL

  ref: NULL

  rows: 74352359

  Extra: Using where

  1 row in set (0.00 sec)

  mysql explain select * from tmp_sampledata where src between 1 and 7 and username = ‘yourusername‘\G

  *************************** 1. row ***************************

  id: 1

  select_type: SIMPLE

  table: tmp_sampledata

  type: ALL

  possible_keys: NULL

  key: NULL

  key_len: NULL

  ref: NULL

  rows: 74352359

  Extra: Using where

  1 row in set (0.00 sec)

  那这个查询就没法用了。根本就没人能等待一个上亿表的全表搜索!这是我们就考虑是否给username创建一个索引,这样肯定会提高查询速度:

  create index idx_username on tmp_sampledata(username);

  这个创建索引的时间很久,似乎超过了数据装载时间,不过好歹建好了。

  复制代码 代码如下:

  mysql explain select * from tmp_sampledata2 where username = ‘yourusername‘\G

  *************************** 1. row ***************************

  id: 1

  select_type: SIMPLE

  table: tmp_sampledata2

  type: ref

  possible_keys: idx_username

  key: idx_username

  key_len: 66

  ref: const

  rows: 80

  Extra: Using where

  1 row in set (0.00 sec)

  和预期的一样,这个查询使用了索引,查询速度在可接受范围内。

  但是这带来了另外一个问题:创建索引需要而外的空间!!当我们对username和email都创建索引时,空间的使用大幅度的提升!这同样不是我们期望看到的(无奈的选择?)。

  除了使用索引,并保证其在查询中能使用到此索引外,分区的关键字段是一个很重要的优化因素,比如下面的这个例子:

  复制代码 代码如下:

  mysql explain select id from tsampledata where username=‘abcdef‘\G

  *************************** 1. row ***************************

  id: 1

  select_type: SIMPLE

  table: tsampledata

  type: ref

  possible_keys: idx_sampledata_username

  key: idx_sampledata_username

  key_len: 66

  ref: const

  rows: 80

  Extra: Using where

  1 row in set (0.00 sec)

  mysql explain select id from tsampledata where username=‘abcdef‘ and src in (2,3,4,5)\G

  *************************** 1. row ***************************

  id: 1

  select_type: SIMPLE

  table: tsampledata

  type: ref

  possible_keys: idx_sampledata_username

  key: idx_sampledata_username

  key_len: 66

  ref: const

  rows: 40

  Extra: Using where

  1 row in set (0.01 sec)

  mysql explain select id from tsampledata where username=‘abcdef‘ and src in (2)\G

  *************************** 1. row ***************************

  id: 1

  select_type: SIMPLE

  table: tsampledata

  type: ref

  possible_keys: idx_sampledata_username

  key: idx_sampledata_username

  key_len: 66

  ref: const

  rows: 10

  Extra: Using where

  1 row in set (0.00 sec)

  mysql explain select id from tsampledata where username=‘abcdef‘ and src in (2,3)\G

  *************************** 1. row ***************************

  id: 1

  select_type: SIMPLE

  table: tsampledata

  type: ref

  possible_keys: idx_sampledata_username

  key: idx_sampledata_username

  key_len: 66

  ref: const

  rows: 20

  Extra: Using where

  1 row in set (0.00 sec)

  同一个查询语句在根据是否针对分区限定做查询时,查询成本相差很大:

  where username=‘abcdef‘ rows: 80

  where username=‘abcdef‘ and src in (2,3,4,5) rows: 40

  where username=‘abcdef‘ and src in (2) rows: 10

  where username=‘abcdef‘ and src in (2,3) rows: 20

  从分析中看出,当根据src(分区表的分区字段)进行查询限定时,被影响的数目(rows)在发生着变化。rows:80代表着需要对8个分区进行搜索。

  改进数据存储:另一种分区格式

  既然在统计应用中,最多用的是通过username, email进行数据查询,那么在表存储时,应该考虑使用username,email进行分区,而不是通过id。因此重新创建分区表,导入数据:

  复制代码 代码如下:

  CREATE TABLE `tmp_sampledata` (

  `id` bigint(20) unsigned NOT NULL,

  `username` varchar(32) NOT NULL DEFAULT ”,

  `passwd` varchar(32) DEFAULT NULL,

  `email` varchar(64) NOT NULL DEFAULT ”,

  `nickname` varchar(32) DEFAULT NULL,

  `siteid` varchar(32) DEFAULT NULL,

  `src` smallint(6) NOT NULL DEFAULT ‘0′,

  primary KEY (`src`,`username`,`email`, `id`)

  ) ENGINE=MyISAM DEFAULT CHARSET=gbk

  PARTITION BY LIST COLUMNS(src)

  SUBPARTITION BY KEY (username,email)

  SUBPARTITIONS 10

  (PARTITION pose VALUES IN (1) ENGINE = MyISAM,

  PARTITION p2736 VALUES IN (2) ENGINE = MyISAM,

  PARTITION p736736 VALUES IN (3) ENGINE = MyISAM,

  PARTITION p3838648 VALUES IN (4) ENGINE = MyISAM,

  PARTITION p842692 VALUES IN (5) ENGINE = MyISAM,

  PARTITION p7575 VALUES IN (6) ENGINE = MyISAM,

  PARTITION p386386 VALUES IN (7) ENGINE = MyISAM,

  PARTITION p62678 VALUES IN (8) ENGINE = MyISAM)?;

  这个定义没什么问题,按照预期,它将根据primary key来进行数据表分区。但是这有一个非常非常严重的性能问题:数据在load data infile的时候,同时对数据进行索引创建。这大大延长了数据装载时间,同样是不可忍受的情况。上面这个例子,如果建表时启用了 primary key 或者 unique key, 在我的测试系统上,load data infile执行了超过12小时。而下面这个:

  复制代码 代码如下:

  CREATE TABLE `tmp_sampledata` (

  `id` bigint(20) unsigned NOT NULL,

  `username` varchar(32) NOT NULL DEFAULT ”,

  `passwd` varchar(32) DEFAULT NULL,

  `email` varchar(64) NOT NULL DEFAULT ”,

  `nickname` varchar(32) DEFAULT NULL,

  `siteid` varchar(32) DEFAULT NULL,

  `src` smallint(6) NOT NULL DEFAULT ‘0′

  ) ENGINE=MyISAM DEFAULT CHARSET=gbk

  PARTITION BY LIST COLUMNS(src)

  SUBPARTITION BY KEY (username,email)

  SUBPARTITIONS 10

  (PARTITION pose VALUES IN (1) ENGINE = MyISAM,

  PARTITION p2736 VALUES IN (2) ENGINE = MyISAM,

  PARTITION p736736 VALUES IN (3) ENGINE = MyISAM,

  PARTITION p3838648 VALUES IN (4) ENGINE = MyISAM,

  PARTITION p842692 VALUES IN (5) ENGINE = MyISAM,

  PARTITION p7575 VALUES IN (6) ENGINE = MyISAM,

  PARTITION p386386 VALUES IN (7) ENGINE = MyISAM,

  PARTITION p62678 VALUES IN (8) ENGINE = MyISAM)?;

  数据装载仅仅用了5分钟:

  mysql load data infile ‘cvsfile.txt‘ into table tmp_sampledata fields terminated by ‘\t‘ escaped by ”;

  Query OK, 74352359 rows affected, 65535 warnings (5 min 23.67 sec)

  Records: 74352359 Deleted: 0 Skipped: 0 Warnings: 51267046

  So,所有的问题,又回到了2.上

  测试查询中的模糊搜索

  对于创建好索引的大数据表,一般般的针对性的查询,应该可以满足需要。但是有些查询可能不能通过索引来发挥效率,比如查询以 163.com 结尾的邮箱:

  select … from … where email like ‘%163.com‘

  即便数据针对 email 建立有索引,上面的查询是用不到那个索引的。如果我们使用的是 oracle,那么还可以建立一个反向索引,但是mysql不支持反向索引。所以如果发生类似的查询,只有两种方案可以:

  通过数据冗余,把需要的字段反转一遍另外保存,并创建一个索引

  这样上面的那个查询可以通过 where email like ‘moc.361%‘ 来完成,但是这个成本(存储、更新)太高昂了

  通过全文检索fulltext来实现。不过mysql同样在分区表上不支持fulltext(或许等待以后的版本吧。)

  自己做分词fulltext

  没有最终方案

  创建一个不含任何索引、键的分区表;

  导入数据;

  创建索引;

  因为创建索引要花很久时间,此处做了个小小调整,提高myisam索引的排序空间为1G(默认是8m):

  mysql set myisam_sort_buffer_size=1048576000;

  Query OK, 0 rows affected (0.00 sec)

  mysql create index idx_username_src on tmp_sampledata (username,src);

  Query OK, 74352359 rows affected (7 min 13.11 sec)

  Records: 74352359 Duplicates: 0 Warnings: 0

  mysql create index idx_email_src on tmp_sampledata (email,src);

  Query OK, 74352359 rows affected (10 min 48.30 sec)

  Records: 74352359 Duplicates: 0 Warnings: 0

  mysql create index idx_src_username_email on tmp_sampledata(src,username,email);

  Query OK, 74352359 rows affected (16 min 5.35 sec)

  Records: 74352359 Duplicates: 0 Warnings: 0

  实际应用中,此表可能不需要这么多索引的,都建立一遍,只是为了展示一下创建的速度而已。

  实际应用中的效果

  存储的问题暂时解决到这里了,接下来经过了一系列的服务器参数调整以及查询的优化,我只能做到在这个超过9千万数据中的查询响应速度控制在1到20毫秒。听上去是个不错的成绩。但是当我们的系统有超过几百个人同时使用时,仍然显的力不从心。或许日后还有机会能更优化这个存储与查询。让我慢慢期待吧。

?

原文地址:https://www.cnblogs.com/qfjavabd/p/10572853.html

时间: 2024-10-09 05:20:49

mysql 超大数据/表管理技巧的相关文章

MySQL查询数据表中数据记录(包括多表查询)

MySQL查询数据表中数据记录(包括多表查询) MySQL查询数据表中数据记录(包括多表查询) 转自:http://www.baike369.com/content/?id=5355 在MySQL中创建数据库的目的是为了使用其中的数据. 使用select查询语句可以从数据库中把数据查询出来. select语句的语法格式如下: select selection_list // 要查询的内容,选择哪些列 from table_list // 从什么表中查询,从何处选择行 where primary_

MySQL中数据表的增操作

今天学习到表的增操作,写个博客总结一下,PS:下面所有的注释都是我在电脑上全部操作完成后,再复制到记事本上添加的.至于在执行的时候可不可以那样添加注释,就有待考证了. 选择库 mysql> show databases;#查看目前有哪些数据库存在 +--------------------+ | Database | +--------------------+ | information_schema | | ceshi_ku | | mysql | | performance_schema

MySQL中数据表的查操作

查询数据表的全部内容 mysql> show tables;#查看当前数据库下的所有表 +--------------------+ | Tables_in_ceshi_ku | +--------------------+ | biao | | ceshi_biao | +--------------------+ 2 rows in set (0.00 sec) mysql> select * from biao;#查看该表所有的内容,*代表通配符,在这里是代表所有的列 +------+-

mysql修改数据表名

在使用mysql时,经常遇到表名不符合规范或标准,但是表里已经有大量的数据了,如何保留数据,只更改表名呢? 可以通过建一个相同的表结构的表,把原来的数据导入到新表中,但是这样视乎很麻烦. 能否简单使用一个SQL语句就搞定呢?当然可以,mysql5.0下我们使用这样的SQL语句就可以了. ALTER  TABLE table_name RENAME TO new_table_name 例如 ALTER  TABLE admin_user RENAME TO a_user mysql修改数据表名

MySQL对数据表进行分组查询(GROUP BY)

MySQL对数据表进行分组查询(GROUP BY) GROUP BY关键字可以将查询结果按照某个字段或多个字段进行分组.字段中值相等的为一组.基本的语法格式如下: GROUP BY 属性名 [HAVING 条件表达式] [WITH ROLLUP] 属性名:是指按照该字段的值进行分组. HAVING 条件表达式:用来限制分组后的显示,符合条件表达式的结果将被显示. WITH ROLLUP:将会在所有记录的最后加上一条记录.加上的这一条记录是上面所有记录的总和. GROUP BY关键字可以和GROU

MySQL为数据表的指定字段插入数据

username not null 没有默认值/有默认值   insert不插入username字段 均不报错 2014年07月23日21:05    百科369 MySQL为数据表的指定字段插入数据 为数据表的指定字段插入数据,就是在INSERT语句中只向部分字段中插入值,而其它字段的值为数据表定义时的默认值. 基本的语法格式如下: INSERT INTO 表名(字段1,字段2,...,字段m) VALUES(值1,值2,...,值m); 字段1,字段2,...,字段m:表示数据表中的字段名称

SqlCollections - 数据表管理

1 --==============================数据表管理=============================== 2 --特殊字符的输入 3 --Tab: char(9) 4 --换行: char(10) 5 --回车: char(13) 6 7 --varchar(n):1个字符占1个字节:nvarchar(n):1个字符占2个字节 8 --'我是Sky'存在varchar字段中,占7个字节:存在nvarchar中,占10个字节 9 10 --创建新表,并指定主键.

3)-MySQL创建数据表

MySQL 创建数据表 创建MySQL数据表需要以下信息: 表名 表字段名 定义每个表字段 语法 以下为创建MySQL数据表的SQL通用语法: create table table_name (column_name column_type); 以下例子中我们将在 RUNOOB 数据库中创建数据表runoob_tbl: create table if not exists `runoob_tbl`( `runoob_id` int unsigned auto_increment, `runoob

mysql(三) 数据表的基本操作操作

mysql(三) 数据表的基本操作操作 创建表,曾删改查,主键,外键,基本数据类型. 1. 创建表 create table 表名( 列名 类型 是否可以为空, 列名 类型 是否可以为空 )ENGINE=InnoDB DEFAULT CHARSET=utf8 例如: 类型解释: 是否可以为空: 是否可空,null表示空,非字符串 not null - 不可空 null - 可空 默认值设置 默认值,创建列时可以指定默认值,当插入数据时如果未主动设置,则自动添加默认值 create table t