如果你对长篇大论没有兴趣,也可以直接看看结果,或许你对结果感兴趣。在实际应用中经过存储、优化可以做到在超过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毫秒。听上去是个不错的成绩。但是当我们的系统有超过几百个人同时使用时,仍然显的力不从心。或许日后还有机会能更优化这个存储与查询。让我慢慢期待吧。

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

  1. MySQL-02 数据表管理

    学习要点 数据类型 数据字段属性 数据表的类型及存储位置 索引 数据表对象管理 数据类型 数据库中的数据类型分为字段类型和值类型,定义如下: 在设计数据表字段的时候,字段类型定义为三大类:数值类.字符 ...

  2. MySQL基本库表管理

    基本管理指令 mysql登陆 第一种 [root@wei ~]# mysql -u root -p 第二种(带参输入) [root@wei ~]# mysql -uroot -proot 注意:每个命 ...

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

    username not null 没有默认值/有默认值   insert不插入username字段 均不报错 2014年07月23日21:05    百科369 MySQL为数据表的指定字段插入数据 ...

  4. MySQL 删除数据表

    MySQL 删除数据表 MySQL中删除数据表是非常容易操作的, 但是你再进行删除表操作时要非常小心,因为执行删除命令后所有数据都会消失. 语法 以下为删除MySQL数据表的通用语法: DROP TA ...

  5. MySQL 创建数据表

    MySQL 创建数据表 创建MySQL数据表需要以下信息: 表名 表字段名 定义每个表字段 语法 以下为创建MySQL数据表的SQL通用语法: CREATE TABLE table_name (col ...

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

    MySQL查询数据表中数据记录(包括多表查询) 在MySQL中创建数据库的目的是为了使用其中的数据. 使用select查询语句可以从数据库中把数据查询出来. select语句的语法格式如下: sele ...

  7. MySQL修改数据表存储引擎的3种方法介绍

    这篇文章主要介绍了MySQL修改数据表存储引擎的3种方法介绍,分别是直接修改.导出导入.创建插入3种方法, 可以参考下   MySQL作为最常用的数据库,经常遇到各种各样的问题.今天要说的就是表存储引 ...

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

    mysql(三) 数据表的基本操作操作 创建表,曾删改查,主键,外键,基本数据类型. 1. 创建表 create table 表名( 列名 类型 是否可以为空, 列名 类型 是否可以为空 )ENGIN ...

  9. MySQL对数据表进行分组查询

    MySQL对数据表进行分组查询(GROUP BY) GROUP BY关键字可以将查询结果按照某个字段或多个字段进行分组.字段中值相等的为一组.基本的语法格式如下: GROUP BY 属性名 [HAVI ...

随机推荐

  1. sql server删除重复数据,保留第一条

    SELECT * FROM EnterpriseDataTools.Enterprise.CompanyMainwhere CompanyNo in (select CompanyNo from En ...

  2. 008-centos服务管理

  3. Sizzle源码分析 (一)

    Sizzle 源码分析 (一) 2.1 稳定 版本 Sizzle 选择器引擎博大精深,下面开始阅读它的源代码,并从中做出标记 .先从入口开始,之后慢慢切入 . 入口函数 Sizzle () 源码 19 ...

  4. Perl中的正则表达式(五)

    正则表达式(Regular Expression),在Perl里边通常也叫做模式(Pattern),用来表示匹配(或不匹配)某个字符串的特征模板. 使用简单模式:若模式匹配的对象是$_的内容,只要把模 ...

  5. lnmp之阿里云源码安装mysql5.7.17

    mysql5.7.17一直号称世界上最好的mysql 那么就在阿里云主机linux安装它(采用的源码安装mysql5.7.17) 我在阿里云主机上安装它 连接阿里云主机 进入,跟我们自己装的虚拟机一毛 ...

  6. Understanding Convolutional Neural Networks for NLP

    When we hear about Convolutional Neural Network (CNNs), we typically think of Computer Vision. CNNs ...

  7. 【tensorflow】pip 安装失败的原因

    linux系统旧版本:   pip install https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.5.0-cp27- ...

  8. 20165207 Exp4 恶意代码分析

    目录 1.实验内容 1.1.系统运行监控 1.1.1.使用命令行创建计划任务 1.1.2.使用命令行借助批处理文件创建计划任务 1.1.3.分析netstat计划任务的最终结果 1.1.4.安装配置s ...

  9. eclipse svn插件 设置自动加锁相关

    eclipse svn插件 设置自动加锁相关 Subclipse 1.10.9 发布,改进说明:SVNKit 1.8.8Exception proof repository sorter. (1616 ...

  10. SQL学习之SQL注入总结

    Sql注入定义: 就是通过把sql命令插入到web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行的sql命令的目的. sql注入分类: 基于联合查询 基于错误回显 基于盲注,分时间盲 ...