ITPub博客

首页 > 数据库 > 数据库开发技术 > HANA数据库大表数据生成及性能体验

HANA数据库大表数据生成及性能体验

原创 数据库开发技术 作者:数据库杂记 时间:2019-09-02 08:56:17 0 删除 编辑


HANA数据库功能的强大自不必说。有一张大表,SMP_CLIENT_LOGS:

CREATE [COLUMN] TABLE "SMP_CLIENT_LOGS"(  "CORRELATIONID" NVARCHAR(64) CS_STRING NOT NULL,  "GUID" NVARCHAR(64) CS_STRING NOT NULL,  "APPLICATIONCONNECTIONID" NVARCHAR(64) CS_STRING,  "APPLICATIONID" NVARCHAR(64) CS_STRING,  "DCCOMPONENT" NVARCHAR(256) CS_STRING,  "LOCATION" NVARCHAR(256) CS_STRING,  "MESSAGETEXT" NCLOB MEMORY THRESHOLD 1000,  "MESSAGECODE" NVARCHAR(16) CS_STRING,  "ROOTCONTEXTID" NVARCHAR(64) CS_STRING,  "SEVERITY" INT CS_INT,  "SOURCENAME" NVARCHAR(1280) CS_STRING,  "TIMESTAMP" LONGDATE CS_LONGDATE,  "TRANSACTIONID" NVARCHAR(64) CS_STRING,  "USERNAME" NVARCHAR(64) CS_STRING,  "SERIAL" INT CS_INT,  PRIMARY KEY (    "CORRELATIONID",    "GUID"  ));


1.   生成数据.   

简单的应该就是不断的执行
INSERT INTO SMP_CLIENT_LOGS SELECT * FROM SMP_CLIENT_LOGS, 但是对于GUID字段之类的,需要一些函数支持。

这里可以使用压测工具JMeter,使用它自带的一些函数,然后多线程并行插入,很快能生成上百万条随机数据。

INSERT 语句如下:

INSERT INTO SMP_CLIENT_LOGS VALUES ('${__UUID}',   '${__UUID}',   '${__RandomString(63, abcdefghij0123456789)}',  '${__RandomString(63, abcdefghij0123456789)}',  '${__RandomString(${__Random(32,200)}, abcdefghij0123456789)}',  '${__RandomString(${__Random(32,200)}, abcdefghij0123456789)}',  '${__RandomString(${__Random(100,2048)}, abcdefghij0123456789)}',  '${__RandomString(15, abcdefghij0123456789)}',  '${__RandomString(${__Random(2,63)}, abcdefghij0123456789)}',  ${__Random(1,6)},  '${__RandomString(${__Random(100,1200)}, abcdefghij0123456789)}',  now(),  '${__RandomString(${__Random(2,63)}, abcdefghij0123456789)}',  '${__RandomString(${__Random(2,63)}, abcdefghij0123456789)}',  ${__Random(1,4)})

这样生成的数据,随机性确实可以得到保证。


当然也可以从已有历史数据当中导出, 再导入:

GRANT EXPORT TO GOMOBILE;EXPORT SMP_CLIENT_LOGS_COLUMN AS BINARY INTO '/home/sean/smp/hana/clientlogs_all';

62017141行数据,只要119秒就导出完成,速度确实很快。


2. 现实中的行、列表方式转换及相关性能影响

在现实当中,可能你没有权限export,  仅能通过有限的脚本并且在系统不崩(运行的服务程序依然能提供服务)的情况下完成平衡转换, 该如何有效完成?
备选方法:

1) ALTER TABLE SMP_CLIENT_LOGS TYPE COLUMN;
2) RENAME SMP_CLIENT_LOGS to SMP_CLIENT_LOGS_ORIG
    CREATE COLUMN TABLE SMP_CLIENT_LOGS ....
    INSERT INTO SMP_CLIENT_LOGS SELECT * FROM SMP_CLIENT_LOGS_ORIG
看起来似乎两个都可以。可是实际上执行起来,发现都无法正常完成。
原因有二:1) 数据量太大,时间上也基本上无法忍受  2) 意外发现,居然会引起内存问题:  transaction rolled back by an internal error: Allocation failed ; $size$=32; $name$=libhdbrskernel.so; $type$=pool; $inuse_count$=****; $allocated_size$=****; $failure_type$=GLOBAL_ALLOCATION_LIMIT

出乎我的意料之外。
一般来说, INSERT INTO这类操作,速度应该很快,不太会引起内存问题。

碰到这种问题,该怎么办?

能想到的,最后成功实验成功的是, 将表中的数据按条件分批段INSERT INTO, 使用的脚本如下:

-- DROP TABLE TMP_MIGRATE_RESULT;DOBEGIN      DECLARE STARTTIME LONGDATE = '2019-08-18 00:00:00';      CREATE TABLE TMP_MIGRATE_RESULT(col1 BIGINT, col2 LONGDATE CS_LONGDATE);      WHILE ($STARTTIME < CURRENT_TIMESTAMP)      DO          INSERT INTO SMP_CLIENT_LOGS SELECT * FROM SMP_CLIENT_LOGS_ORIG WHERE TIMESTAMP <= $STARTTIME;          DELETE FROM SMP_CLIENT_LOGS_ORIG WHERE TIMESTAMP <= $STARTTIME;          -- SELECT ::ROWCOUNT FROM DUMMY;          -- Just save the affected row count into the result table          -- Don't forget to drop the result table after migration          INSERT INTO TMP_MIGRATE_RESULT VALUES(::ROWCOUNT, $STARTTIME);          COMMIT;          STARTTIME = ADD_SECONDS($STARTTIME, 3600);      END WHILE;END;

上表相当于按每个小时的数据量依次插入,经考查,按小时分段的数据量,最多也就不到2000000条。这样系统的负载会大大减轻。最终6000多万条数据在60来分钟内完成转换。


同样,删除清理数据,可以使用下边的脚本来完成:

DOBEGIN      DECLARE STARTTIME LONGDATE = '2019-08-15 20:00:23';      DECLARE ENDTIME LONGDATE = '2019-08-27 00:00:00';      CREATE TABLE TMP_MIGRATE_RESULT(col1 BIGINT, col2 LONGDATE CS_LONGDATE);      WHILE ($STARTTIME < $ENDTIME)      DO          DELETE FROM SMP_CLIENT_LOGS WHERE TIMESTAMP <= $STARTTIME;          -- SELECT ::ROWCOUNT FROM DUMMY;          -- Just save the affected row count into the result table          -- Don't forget to drop the result table after migration          INSERT INTO TMP_MIGRATE_RESULT VALUES(::ROWCOUNT, $STARTTIME);          COMMIT;          STARTTIME = ADD_SECONDS($STARTTIME, 3600);      END WHILE;            -- last time      DELETE FROM SMP_CLIENT_LOGS WHERE TIMESTAMP <= $ENDTIME;      -- SELECT ::ROWCOUNT FROM DUMMY;      -- Just save the affected row count into the result table      -- Don't forget to drop the result table after migration      INSERT INTO TMP_MIGRATE_RESULT VALUES(::ROWCOUNT, $ENDTIME);      COMMIT;      END;

虽然有些诡异,但比较安全。


3. 相关系统开销的监控: 

1) 磁盘空间开销:

SELECT SCHEMA_NAME,TABLE_NAME,DISK_SIZE FROM PUBLIC.M_TABLE_PERSISTENCE_STATISTICS ORDER BY DISK_SIZE DESC

2) 列式表开销:

select SCHEMA_NAME as "Schema", round(sum(MEMORY_SIZE_IN_TOTAL) /1024/1024) as "MB Used" from M_CS_TABLES GROUP by SCHEMA_NAME order by "MB Used" DESC

3) 行式表开销:

select SCHEMA_NAME as "Schema", round(sum(USED_FIXED_PART_SIZE + USED_VARIABLE_PART_SIZE) /1024/1024) as "MB Used" from M_RS_TABLES GROUP by SCHEMA_NAME order by "MB Used" desc

4) 当前相关查询及连接的开销:  (没发现有多好用)

selectc.host, c.user_name, c.connection_status, c.transaction_id, s.last_executed_time,round(s.allocated_memory_size/1024,2) as "Alloc Mem (KB)",round(s.used_memory_size/1024,2) as "Used Mem (KB)", s.statement_stringfromm_connections c, m_prepared_statements swheres.connection_id = c.connection_id and c.connection_status != 'IDLE'order bys.allocated_memory_size desc;


4. 关于分区表:

仔细查检了相关官方文档, HANA虽然支持的分区方式蛮多,但是分区表达式支持的并不太多。

可以基于timestamp的YEAR, MONTH, 但是没有基于HOUR的和 dayname dayname的。想想也对,hour的话,前一天的数据和当天的数据有可能就在同一个分区里头了。上边的这个表,数据量确实过于庞大,两三天的数据就达到6000来万条。一个月的量可以达到9亿多条。这6000来万条是17G数据量,一个月可以达到300G左右。

不过,可以做一个冗余列来把数据分散到多个分区里头:

CREATE COLUMN TABLE "SMP_CLIENT_LOGS2"(      "CORRELATIONID" NVARCHAR(64) CS_STRING NOT NULL,      "GUID" NVARCHAR(64) CS_STRING NOT NULL,      "TIMESTAMP" LONGDATE CS_LONGDATE,      "TRANSACTIONID" NVARCHAR(64) CS_STRING,      "USERNAME" NVARCHAR(64) CS_STRING,      "SERIAL" INT CS_INT,      "DAYV" INT CS_INT,      PRIMARY KEY (            "CORRELATIONID",            "GUID",            "DAYV"      )) PARTITION BY RANGE(DAYV) (PARTITION VALUES=0,       PARTITION  VALUES=1,       PARTITION  VALUES=2,      PARTITION  VALUES=3,      PARTITION  VALUES=4,      PARTITION  VALUES=5,      PARTITION  VALUES=6      ) ;

上边是按照一周的第几天来分的。假如系统始终只保留6天的数据,那就比较好玩了。比如,现在是周6, 那就可以把partition value = 0的数据全部archive掉 (archive的功能可以单独实现),非常简洁。

5. 关于SELECT 语句引起的内存问题
总结以后,发现还是跟返回的数据量有些关系。 

SELECT * FROM SMP_CLIENT_LOGSSELECT * FROM SMP_CLIENT_LOGS ORDER BY timestampSELECT * FROM SMP_CLIENT_LOGS WHERE SECONDS_BETWEEN(timestamp, '2019-08-26 05:00:00') BETWEEN 0 AND 7200"
SELECT * FROM SMP_CLIENT_LOGS WHERE TO_VARCHAR(timestamp, 'YYYY-MM-DD HH') = '2019-08-25 05'

结果发现,前边两种都会挂:

Exception in thread "main" com.sap.db.jdbc.exceptions.jdbc40.SQLTransactionRollbackException: [129]: transaction rolled back by an internal error: Allocation failed ; $size$=32; $name$=libhdbrskernel.so; $type$=pool; $inuse_count$=11223397; $allocated_size$=565437665; $failure_type$=GLOBAL_ALLOCATION_LIMIT  at com.sap.db.jdbc.exceptions.jdbc40.SQLTransactionRollbackException.createException(SQLTransactionRollbackException.java:40)  at com.sap.db.jdbc.exceptions.SQLExceptionSapDB.createException(SQLExceptionSapDB.java:304)  at com.sap.db.jdbc.exceptions.SQLExceptionSapDB.generateDatabaseException(SQLExceptionSapDB.java:174)  at com.sap.db.jdbc.packet.ReplyPacket.buildExceptionChain(ReplyPacket.java:104)  at com.sap.db.jdbc.ConnectionSapDB.execute(ConnectionSapDB.java:1142)  at com.sap.db.jdbc.ConnectionSapDB.execute(ConnectionSapDB.java:886)  at com.sap.db.jdbc.FetchInfo.executeFetch(FetchInfo.java:162)  at com.sap.db.jdbc.FetchInfo.executeFetchNext(FetchInfo.java:157)  at com.sap.db.jdbc.ResultSetSapDB.fetchNextChunk(ResultSetSapDB.java:2424)  at com.sap.db.jdbc.ResultSetSapDB.next(ResultSetSapDB.java:419)  at com.sap.db.jdbc.trace.ResultSet.next(ResultSet.java:269)  at com.foo.db.ClientLogMemory.testSQL(ClientLogMemory.java:69)  at com.foo.db.ClientLogMemory.main(ClientLogMemory.java:107)


原文作者:数据库杂记;如有侵权,请联系管理员删除。

下一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2019-04-22

  • 博文量
    276
  • 访问量
    160270