oracle 如何保持具有数十亿行的表的数据库性能

a9wyjsp7  于 2023-10-16  发布在  Oracle
关注(0)|答案(1)|浏览(132)

我在一家科技公司担任产品经理(具有坚实的SQL背景),该公司专门为ICU和麻醉开发患者数据管理系统。到目前为止,我们的客户群主要是中小型医院,比如说最多20张病床,每分钟产生数据,但现在有机会销售给大型/大学医院,大约200张重症监护病床连接到多种医疗设备(监视器,呼吸机,透析,注射泵),我们计划推出新一代产品。
问题:我们收到了很多关于性能不佳的客户电话,并且在即将到来的产品中不会变得更好。系统会随着时间而变慢。
对于大客户,我计算了200张床,平均每张床每分钟30排。假设占用率为75%,每年总计约24亿行。每年约有3000例患者。
到目前为止,我们将每个输入,无论其来源(设备或用户)和数据类型(数字,文本,引用,日期和日期范围)都存储在一个充满索引的表中。我们的开发人员喜欢这样。
我的计划是根据数据类型将数据分离到不同的表中,因为我的印象是索引将更有针对性,我也用竞争对手的数据库进行了验证,它们完全是这样做的。
应用程序查询是非常基本的,主要如下所示

SELECT parameter_id, date_time, value
FROM data_numeric 
WHERE visit_id = :visit_id 
AND parameter_id = :parameter_id -- e.g. the heart rate
AND date_time >= :from_date_time
AND date_time < :to_date_time

一次没有超过50行的大容量插入或更新。
有什么想法或经验,我们应该如何开始?在这种情况下,索引组织的表或其他概念(如分区)是否有帮助?
感谢你的帮助!

9nvpjoqh

9nvpjoqh1#

您应该考虑坚持使用一个大表和索引架构。该解决方案可能比拆分表和索引更快。也许你有一些棘手的实现问题,阻止你的索引扩展?

一个索引统治所有索引(理论上)

B树索引的性能呈几何增长。如果主行只有一个数据点,从单个大索引进行阅读将花费O(N))磁盘读取。现在,假设您将该大索引拆分为10个较小的索引。从一个较小的索引中阅读将花费O(n(N/10)),这将只为您节省保存一次读取。
现在想象一下同样的场景,但是有两个数据点要读取。对于单个索引,阅读两个数据点可能仍然需要O(N)磁盘读取。这是因为这两个值将紧挨着存储,可能在同一个数据库块中。相比之下,从两个较小的索引中阅读两个数据点,其成本为2 * O(N(N/10)),几乎是两倍。

更真实的例子

如果上面的运行时分析看起来太抽象,下面是一个更实际的示例,演示了大表和索引策略如何优于多个小表和索引策略。
下面的代码创建了240 M行的数值数据和2.4M行的字符串数据。(我的系统无法处理完整的2.4B行,所以我将其缩小到1/10。)正如您所期望的,数值上的2.4亿行索引大约是字符串值上的2.4亿行索引的100倍。但是从每个索引中阅读单行具有非常相似的性能:4个磁盘读取数字,3个磁盘读取字符串。
如果您要查找单次访视或床的所有EAV数据,则您将花费更多的时间阅读1%的数据索引,而不是花费阅读99%的数据索引。如果是这样的话,你最好把所有的东西都放在一个表和一个索引中。
在许多情况下,一个大索引优于多个小索引。也许这不适用于您-也许您的索引访问不是我所假设的。但至少值得一试。了解对数性能 * 应该 * 如何发生可能有助于您找到现有索引的问题。

DATA_NUMERIC的2.4亿行代码。BLEVEL+1 = 4次读取。

--Create table and sequence for sample data.
create table data_numeric
(
    data_numeric_id number primary key,
    visit_id        number,
    parameter_id    number,
    date_time       date,
    value           number
) nologging;
create sequence data_numeric_id_seq;

--Load 240M rows into table.
begin
    --for i in 1 .. 2400 loop
        insert /*+ append */ into data_numeric
        select
            data_numeric_id_seq.nextval,
            mod(level, 1000)         visit_id,     --1K visits for every 100K data points.
            mod(level, 100)          parameter_id, --100 different parameters.
            sysdate + level/86400    date_time,    --Each row has a different second.
            dbms_random.value * 1000 value         --Random numeric values.
        from dual connect by level <= 100000;
        
        commit;
    end loop;
end;
/

--Create index.
create index data_numeric_idx on data_numeric(visit_id, date_time) nologging parallel 8;
alter index data_numeric_idx noparallel;

--Number of reads to get one index block: 4
select blevel+1 from dba_indexes where index_name = 'DATA_NUMERIC_IDX';
--Table: 12771 MB
select bytes/1024/1024 mb from dba_segments where segment_name = 'DATA_NUMERIC';
--Index:  6112 MB
select bytes/1024/1024 mb from dba_segments where segment_name = 'DATA_NUMERIC_IDX';

2.4M行STRING_NUMERIC的代码。BLEVEL+1 = 3次读取。

--Create table and sequence for sample data.
create table data_string
(
    data_string_id  number primary key,
    visit_id        number,
    parameter_id    number,
    date_time       date,
    value           varchar2(100)
) nologging;
create sequence data_string_id_seq;

--Load 2.4 rows into table.
begin
    for i in 1 .. 24 loop
        insert /*+ append */ into data_string
        select
            data_string_id_seq.nextval,
            mod(level, 1000)                  visit_id,     --1K visits for every 100K data points.
            mod(level, 100)                   parameter_id, --100 different parameters.
            sysdate + level/86400             date_time,    --Each row has a different second.
            dbms_random.string('a', len => 10) value         --Random strings values.
        from dual connect by level <= 100000;
        
        commit;
    end loop;
end;
/

--Create index.
create index data_string_idx on data_string(visit_id, date_time) nologging parallel 8;
alter index data_string_idx noparallel;

--Number of reads to get one index block: 3
select blevel+1 from dba_indexes where index_name = 'DATA_STRING_IDX';
--Table: 96 MB
select bytes/1024/1024 mb from dba_segments where segment_name = 'DATA_STRING';
--Index:  61 MB
select bytes/1024/1024 mb from dba_segments where segment_name = 'DATA_STRING_IDX';

相关问题