大数据分析中使用关系型数据库的关键点

相当一部分大数据分析处理的原始数据来自关系型数据库,处理结果也存放在关系型数据库中。原因在于超过99%的软件系统采用传统的关系型数据库,大家对它们很熟悉,用起来得心应手。

在我们正式的大数据团队,数仓(数据仓库Hive+HBase)的数据收集同样来自Oracle或MySql,处理后的统计结果和明细,尽管保存在Hive中,但也会定时推送到Oracle/MySql,供前台系统读取展示,生成各种报表。

在这种场景下,数据库的读写性能就显得尤为重要!

 

一、数据库定位

有大神说,给我足够强的数据库硬件,一个GroupBy就可以满足各种统计分析场景。

这话不假,我们一台数百万的金融级别Oracle一体机证明了GroupBy可以做得很强大,同时也证明了它有天花板,就是当数据更大的时候,它依然得趴下!

于是,我们需要有设计原则,有优化技巧。

 

核心原则:数据库只是数据存储的载体,在大数据中难以利用它的计算能力!

有了这个原则,就意味着数据库将会用得“纯粹”:

  • 数据表独立性很强,大表间很少join(这让我想起有同学在Hive里对两张大表做笛卡尔乘积产生270T数据)
  • 数据表很大,单表几十亿行很常见
  • 索引很少,一般按主键查单行或者按时间查一段

二、分区存储

 在这里,数据库就是存储数据的仓库,海量数据需要拆分存储,不可能全都挤一块。

根据业务不同,一般有两种拆分方式:

  1. 单表分区。常见于Oracle,每月做一个分区,数据连续方便业务处理,但要求单机性能强劲。
  2. 分表分库。常见于MySql,分个128张表乃至4096张表也都是很平常的事情,可以用很多性能较差的机器组建集群,但因数据不连续不便于业务处理。

具体采用哪一种拆分方式,由使用场景决定。

如果以后还要整体抽出来去做统计分析,比如原始数据和中间数据,那么优先考虑做分区。既方便连续抽取,又方便按月删除历史数据,对海量数据Delete很痛苦。分区内还可以建立子分区和分区内索引。

如果用于业务数据或者最终统计结果,那么考虑分库后分表,按照业务维度把数据“均匀”存在不同表上。比如对单号取CRC,然后对数据表数取模。

 

有很多数据,属于时序数据性质,或者日志型,都是只有插入,只有少量或者完全没有Update,几乎没有Delete。

这种数据有个很关键的时间字段,确定数据什么时候到来,比如InputDate/CreateTime/UpdateTime,可以借助触发器给这个字段填充当前时间。

基于时间维度抽取时序数据进行分析时,必须确保时间字段升序能够查到所有数据,不会漏过也不会重复查某些行。

三、高效查询

 海量数据查询,必须100%确定命中索引。要么是code=xxx,要么是 updatetime>=:start and updatetime<:end。

根据主键查询,命中单行或少量数据;

根据时间查询,必须合理选择时间区间(start, end),让查询结果控制在10000~20000行左右较好。

比如考虑到高峰时段,我们一般取5秒的区间进行查询,一般得到10000~40000行。

 

使用数据时,可能有很多查询条件,但其中最重要的一般是时间区间。

因为数据很大,DBMS本身的统计信息收集工作可能很不及时,导致执行计划选择错误的索引方案,这种情况下需要手工收集信息,甚至在查询语句里面强制指定索引。

四、批量写入

借助内存计算,我们往往可以在很短的时间内计算得到数十万乃至数百万数据,需要写入数据库。

一般数据库的Insert/Update性能只有3000~5000tps,带着索引的负担,难以快速把数据写入其中。

这里以Oracle为例,它的OracleCommand有一个超强功能ArrayBindCount,可以对一次参数化写入操作绑定多组(例如5000组/行)。

该方法能够让它得到最高写入性能,实际业务使用得到30000tps左右。

var count = 1_000_000;
var connectStr = "User Id=scott;Password=tiger;Data Source=";

var conn = new OracleConnection(connectStr);
var command = new OracleCommand
{
    Connection = conn,
    ArrayBindCount = count,
    CommandText = "insert into dept values(:deptno, :deptname, :loc)"
};
conn.Open();

var deptNo = new Int32[count];
var dname = new String[count];
var loc = new String[count];

var deptNoParam = new OracleParameter("deptno", OracleDbType.Int32)
{
    Direction = ParameterDirection.Input,
    Value = deptNo
};
command.Parameters.Add(deptNoParam);

var deptNameParam = new OracleParameter("deptname", OracleDbType.Varchar2)
{
    Direction = ParameterDirection.Input,
    Value = dname
};
command.Parameters.Add(deptNameParam);

var deptLocParam = new OracleParameter("loc", OracleDbType.Varchar2)
{
    Direction = ParameterDirection.Input,
    Value = loc
};
command.Parameters.Add(deptLocParam);

var sw = Stopwatch.StartNew();
for (var i = 0; i < count; i++)
{
    deptNo[i] = i;
    dname[i] = i.ToString();
    loc[i] = i.ToString();
}

command.ExecuteNonQuery();

sw.Stop();

Debug.WriteLine("批量插入:" + count + "所占时间:" + sw.ElapsedMilliseconds);

MySql和SQLite都有它独特的批量写入功能,并且支持netcore。

SqlServer也有批量写入功能,但是目前还不支持netcore。

MySql方案另起一篇文章专门写。

 

 五、总结

关系型数据库存储大数据,要点就是:简单存储、分区分表、高效索引、批量写入!

 

100亿小数据实时计算平台(大数据系列目录):

1,大数据分析中使用关系型数据库的关键点

2,MySql如何做到600000tps的极速批量写入

3,大数据分析中Redis经验分享

4,如何分批处理大数据(调度系统)

新生命Redis组件(日均80亿次调用)

借助Redis做秒杀和限流的思考

大数据分析中Redis怎么做到220万ops

每天4亿行SQLite订单大数据测试(源码)

 

End.

posted @ 2018-09-13 23:49 大石头 阅读(...) 评论(...) 编辑 收藏