大数据分析中使用关系型数据库的关键点
相当一部分大数据分析处理的原始数据来自关系型数据库,处理结果也存放在关系型数据库中。原因在于超过99%的软件系统采用传统的关系型数据库,大家对它们很熟悉,用起来得心应手。
在我们正式的大数据团队,数仓(数据仓库Hive+HBase)的数据收集同样来自Oracle或MySql,处理后的统计结果和明细,尽管保存在Hive中,但也会定时推送到Oracle/MySql,供前台系统读取展示,生成各种报表。
在这种场景下,数据库的读写性能就显得尤为重要!
一、数据库定位
有大神说,给我足够强的数据库硬件,一个GroupBy就可以满足各种统计分析场景。
这话不假,我们一台数百万的金融级别Oracle一体机证明了GroupBy可以做得很强大,同时也证明了它有天花板,就是当数据更大的时候,它依然得趴下!
于是,我们需要有设计原则,有优化技巧。
核心原则:数据库只是数据存储的载体,在大数据中难以利用它的计算能力!
有了这个原则,就意味着数据库将会用得“纯粹”:
- 数据表独立性很强,大表间很少join(这让我想起有同学在Hive里对两张大表做笛卡尔乘积产生270T数据)
- 数据表很大,单表几十亿行很常见
- 索引很少,一般按主键查单行或者按时间查一段
二、分区存储
在这里,数据库就是存储数据的仓库,海量数据需要拆分存储,不可能全都挤一块。
根据业务不同,一般有两种拆分方式:
- 单表分区。常见于Oracle,每月做一个分区,数据连续方便业务处理,但要求单机性能强劲。
- 分表分库。常见于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亿小数据实时计算平台(大数据系列目录):
2,MySql如何做到600000tps的极速批量写入
3,大数据分析中Redis经验分享
4,如何分批处理大数据(调度系统)
End.