MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

MySQL汇总表构建与优化技巧

2022-04-061.4k 阅读

MySQL汇总表概述

在数据库应用中,经常会遇到需要对大量数据进行汇总统计的场景。例如,在电商系统中统计每日的订单金额、订单数量,在日志系统中统计不同类型日志的数量等。MySQL汇总表就是为了高效处理这类需求而存在的。

汇总表是一种预先计算并存储聚合数据的表。它通过对基础表中的数据进行聚合操作(如SUM、COUNT、AVG等),将结果存储在新的表中。这样,当需要获取汇总数据时,直接查询汇总表,而无需每次都对大量的基础数据进行实时计算,大大提高了查询性能。

构建汇总表的基本步骤

  1. 确定需求:明确需要汇总的数据以及聚合方式。例如,要统计每个月的销售总额,聚合方式就是对销售额进行每月的SUM操作。
  2. 设计汇总表结构:根据需求设计汇总表的列。一般来说,汇总表应包含用于分组的列(如日期中的月份)和用于存储聚合结果的列(如销售总额)。
  3. 创建汇总表:使用CREATE TABLE语句创建汇总表。
  4. 填充汇总表数据:通过INSERT INTO...SELECT语句从基础表中获取数据并进行聚合计算,插入到汇总表中。

示例:构建订单汇总表

假设我们有一个orders表,结构如下:

CREATE TABLE orders (
    order_id INT PRIMARY KEY AUTO_INCREMENT,
    order_date DATE,
    customer_id INT,
    order_amount DECIMAL(10, 2)
);

现在我们要构建一个汇总表,统计每个月的订单总金额和订单数量。

  1. 创建汇总表
CREATE TABLE monthly_order_summary (
    month_year CHAR(7),
    total_amount DECIMAL(10, 2),
    order_count INT,
    PRIMARY KEY (month_year)
);
  1. 填充汇总表数据
INSERT INTO monthly_order_summary (month_year, total_amount, order_count)
SELECT
    DATE_FORMAT(order_date, '%Y-%m') AS month_year,
    SUM(order_amount) AS total_amount,
    COUNT(*) AS order_count
FROM
    orders
GROUP BY
    DATE_FORMAT(order_date, '%Y-%m');

汇总表的更新策略

  1. 定期更新:根据业务需求,设定固定的时间间隔(如每天凌晨、每周日等)来更新汇总表。可以使用MySQL的事件调度器(Event Scheduler)来实现自动更新。
    • 开启事件调度器
SET GLOBAL event_scheduler = ON;
- **创建事件**:例如,每天凌晨更新订单汇总表。
CREATE EVENT update_monthly_order_summary
ON SCHEDULE EVERY 1 DAY
STARTS '2024-01-01 00:00:00'
DO
BEGIN
    INSERT INTO monthly_order_summary (month_year, total_amount, order_count)
    SELECT
        DATE_FORMAT(order_date, '%Y-%m') AS month_year,
        SUM(order_amount) AS total_amount,
        COUNT(*) AS order_count
    FROM
        orders
    WHERE
        DATE_FORMAT(order_date, '%Y-%m') NOT IN (SELECT month_year FROM monthly_order_summary)
    GROUP BY
        DATE_FORMAT(order_date, '%Y-%m');
END;
  1. 实时更新:当基础表数据发生变化(插入、更新、删除)时,立即更新汇总表。这种方式可以通过触发器(Trigger)来实现。
    • 插入触发器示例:当有新订单插入时,更新汇总表。
DELIMITER //
CREATE TRIGGER after_order_insert
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
    DECLARE order_month CHAR(7);
    SET order_month = DATE_FORMAT(NEW.order_date, '%Y-%m');
    IF NOT EXISTS (SELECT 1 FROM monthly_order_summary WHERE month_year = order_month) THEN
        INSERT INTO monthly_order_summary (month_year, total_amount, order_count)
        VALUES (order_month, NEW.order_amount, 1);
    ELSE
        UPDATE monthly_order_summary
        SET total_amount = total_amount + NEW.order_amount,
            order_count = order_count + 1
        WHERE month_year = order_month;
    END IF;
END //
DELIMITER ;
- **更新触发器示例**:当订单金额更新时,更新汇总表。
DELIMITER //
CREATE TRIGGER after_order_update
AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
    DECLARE order_month CHAR(7);
    SET order_month = DATE_FORMAT(NEW.order_date, '%Y-%m');
    IF NEW.order_amount != OLD.order_amount THEN
        UPDATE monthly_order_summary
        SET total_amount = total_amount - OLD.order_amount + NEW.order_amount
        WHERE month_year = order_month;
    END IF;
END //
DELIMITER ;
- **删除触发器示例**:当订单删除时,更新汇总表。
DELIMITER //
CREATE TRIGGER after_order_delete
AFTER DELETE ON orders
FOR EACH ROW
BEGIN
    DECLARE order_month CHAR(7);
    SET order_month = DATE_FORMAT(OLD.order_date, '%Y-%m');
    UPDATE monthly_order_summary
    SET total_amount = total_amount - OLD.order_amount,
        order_count = order_count - 1
    WHERE month_year = order_month;
    IF (SELECT order_count FROM monthly_order_summary WHERE month_year = order_month) = 0 THEN
        DELETE FROM monthly_order_summary WHERE month_year = order_month;
    END IF;
END //
DELIMITER ;

汇总表优化技巧

  1. 索引优化
    • 分组列索引:在汇总表中,用于分组的列(如日期中的月份)应创建索引。这样在查询汇总数据时,可以快速定位到相应的分组数据。
CREATE INDEX idx_month_year ON monthly_order_summary(month_year);
- **关联列索引**:如果汇总表需要与其他表进行关联查询,关联列也应创建索引。例如,汇总表中如果包含`customer_id`用于关联`customers`表,那么在`monthly_order_summary`表的`customer_id`列上创建索引。
CREATE INDEX idx_customer_id ON monthly_order_summary(customer_id);
  1. 数据分区
    • 范围分区:对于按日期等范围进行汇总的表,可以使用范围分区。例如,按月份对订单汇总表进行分区。
CREATE TABLE monthly_order_summary (
    month_year CHAR(7),
    total_amount DECIMAL(10, 2),
    order_count INT,
    PRIMARY KEY (month_year)
)
PARTITION BY RANGE (YEAR(STR_TO_DATE(month_year, '%Y-%m')) * 100 + MONTH(STR_TO_DATE(month_year, '%Y-%m'))) (
    PARTITION p0 VALUES LESS THAN (202401),
    PARTITION p1 VALUES LESS THAN (202402),
    PARTITION p2 VALUES LESS THAN (202403),
    -- 根据需要添加更多分区
    PARTITION pn VALUES LESS THAN (MAXVALUE)
);
- **哈希分区**:当数据分布比较均匀,且没有明显的范围特征时,可以使用哈希分区。例如,按订单ID进行哈希分区。
CREATE TABLE order_summary_hash (
    order_id INT,
    total_amount DECIMAL(10, 2),
    PRIMARY KEY (order_id)
)
PARTITION BY HASH (order_id)
PARTITIONS 4;
  1. 汇总表结构优化
    • 避免冗余列:汇总表应只包含必要的列,避免包含过多无关信息,以减少存储空间和提高查询性能。
    • 数据类型优化:选择合适的数据类型。例如,对于订单数量可以使用INT类型,对于金额可以使用DECIMAL类型,并根据实际数据范围设置合适的精度和标度。

复杂汇总场景处理

  1. 多层汇总:有时候需要进行多层汇总。例如,先按日统计订单金额,再按月对每日的统计结果进行汇总。
    • 创建日汇总表
CREATE TABLE daily_order_summary (
    order_date DATE,
    total_amount DECIMAL(10, 2),
    order_count INT,
    PRIMARY KEY (order_date)
);
- **填充日汇总表数据**:
INSERT INTO daily_order_summary (order_date, total_amount, order_count)
SELECT
    order_date,
    SUM(order_amount) AS total_amount,
    COUNT(*) AS order_count
FROM
    orders
GROUP BY
    order_date;
- **创建月汇总表(基于日汇总表)**:
CREATE TABLE monthly_order_summary_from_daily (
    month_year CHAR(7),
    total_amount DECIMAL(10, 2),
    order_count INT,
    PRIMARY KEY (month_year)
);
- **填充月汇总表数据**:
INSERT INTO monthly_order_summary_from_daily (month_year, total_amount, order_count)
SELECT
    DATE_FORMAT(order_date, '%Y-%m') AS month_year,
    SUM(total_amount) AS total_amount,
    SUM(order_count) AS order_count
FROM
    daily_order_summary
GROUP BY
    DATE_FORMAT(order_date, '%Y-%m');
  1. 条件汇总:在汇总时添加条件。例如,只统计金额大于100的订单的汇总数据。
    • 创建汇总表
CREATE TABLE high_value_order_summary (
    month_year CHAR(7),
    total_amount DECIMAL(10, 2),
    order_count INT,
    PRIMARY KEY (month_year)
);
- **填充汇总表数据**:
INSERT INTO high_value_order_summary (month_year, total_amount, order_count)
SELECT
    DATE_FORMAT(order_date, '%Y-%m') AS month_year,
    SUM(order_amount) AS total_amount,
    COUNT(*) AS order_count
FROM
    orders
WHERE
    order_amount > 100
GROUP BY
    DATE_FORMAT(order_date, '%Y-%m');

汇总表与缓存结合

  1. 缓存汇总数据:可以使用Redis等缓存工具来缓存汇总表中的数据。当有查询请求时,先从缓存中获取数据,如果缓存中没有,则查询汇总表,并将结果存入缓存。
    • 示例代码(使用Python和Redis)
import redis
import mysql.connector

redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)

def get_monthly_order_summary(month_year):
    data = redis_client.get(month_year)
    if data:
        return data.decode('utf-8')

    conn = mysql.connector.connect(user='root', password='password', host='127.0.0.1', database='your_database')
    cursor = conn.cursor()
    query = "SELECT total_amount, order_count FROM monthly_order_summary WHERE month_year = %s"
    cursor.execute(query, (month_year,))
    result = cursor.fetchone()
    conn.close()

    if result:
        data = f"Total Amount: {result[0]}, Order Count: {result[1]}"
        redis_client.set(month_year, data)
        return data
    return "Data not found"
  1. 缓存更新策略:当汇总表数据更新时,需要及时更新缓存。可以在汇总表更新的触发器或事件中添加缓存更新逻辑。
    • 示例代码(在MySQL触发器中更新Redis缓存)
DELIMITER //
CREATE TRIGGER after_monthly_summary_update
AFTER UPDATE ON monthly_order_summary
FOR EACH ROW
BEGIN
    SET @redis_command = CONCAT('SET ', NEW.month_year,'"Total Amount: ', NEW.total_amount,'Order Count: ', NEW.order_count,'"');
    SET @redis_result = sys_exec('redis-cli ', @redis_command);
END //
DELIMITER ;

汇总表在大数据量下的挑战与应对

  1. 数据量增长:随着业务的发展,基础表数据量不断增加,汇总表的更新和查询性能可能会受到影响。
    • 优化查询语句:使用EXPLAIN关键字分析查询语句,优化索引使用和查询执行计划。
EXPLAIN INSERT INTO monthly_order_summary (month_year, total_amount, order_count)
SELECT
    DATE_FORMAT(order_date, '%Y-%m') AS month_year,
    SUM(order_amount) AS total_amount,
    COUNT(*) AS order_count
FROM
    orders
GROUP BY
    DATE_FORMAT(order_date, '%Y-%m');
- **批量处理**:在更新汇总表时,采用批量插入或更新的方式,减少数据库交互次数。例如,一次处理1000条数据。
INSERT INTO monthly_order_summary (month_year, total_amount, order_count)
SELECT
    DATE_FORMAT(order_date, '%Y-%m') AS month_year,
    SUM(order_amount) AS total_amount,
    COUNT(*) AS order_count
FROM
    orders
WHERE
    order_id BETWEEN 1 AND 1000
GROUP BY
    DATE_FORMAT(order_date, '%Y-%m');
  1. 存储压力:大量的汇总数据可能导致存储压力增大。
    • 数据归档:对于历史数据,可以定期归档到低成本的存储介质(如磁带)。例如,将一年前的汇总数据归档。
    • 压缩存储:MySQL支持表数据压缩,可以通过设置ROW_FORMAT=COMPRESSED来压缩汇总表数据。
CREATE TABLE monthly_order_summary (
    month_year CHAR(7),
    total_amount DECIMAL(10, 2),
    order_count INT,
    PRIMARY KEY (month_year)
)
ROW_FORMAT=COMPRESSED;

跨表汇总

  1. 关联多个基础表进行汇总:在实际业务中,可能需要从多个相关的基础表中获取数据并进行汇总。例如,orders表和order_items表,orders表存储订单基本信息,order_items表存储订单中的商品明细。要统计每个月的商品销售总数量和总金额。
    • 假设表结构
CREATE TABLE orders (
    order_id INT PRIMARY KEY AUTO_INCREMENT,
    order_date DATE
);

CREATE TABLE order_items (
    item_id INT PRIMARY KEY AUTO_INCREMENT,
    order_id INT,
    product_id INT,
    quantity INT,
    price DECIMAL(10, 2),
    FOREIGN KEY (order_id) REFERENCES orders(order_id)
);
- **创建汇总表**:
CREATE TABLE monthly_product_summary (
    month_year CHAR(7),
    total_quantity INT,
    total_amount DECIMAL(10, 2),
    PRIMARY KEY (month_year)
);
- **填充汇总表数据**:
INSERT INTO monthly_product_summary (month_year, total_quantity, total_amount)
SELECT
    DATE_FORMAT(o.order_date, '%Y-%m') AS month_year,
    SUM(oi.quantity) AS total_quantity,
    SUM(oi.quantity * oi.price) AS total_amount
FROM
    orders o
JOIN
    order_items oi ON o.order_id = oi.order_id
GROUP BY
    DATE_FORMAT(o.order_date, '%Y-%m');
  1. 处理复杂关联关系:如果基础表之间存在复杂的关联关系,如多对多关系,需要通过中间表进行关联并汇总。例如,products表、orders表和中间表order_products
    • 假设表结构
CREATE TABLE products (
    product_id INT PRIMARY KEY AUTO_INCREMENT,
    product_name VARCHAR(100)
);

CREATE TABLE orders (
    order_id INT PRIMARY KEY AUTO_INCREMENT,
    order_date DATE
);

CREATE TABLE order_products (
    order_product_id INT PRIMARY KEY AUTO_INCREMENT,
    order_id INT,
    product_id INT,
    quantity INT,
    price DECIMAL(10, 2),
    FOREIGN KEY (order_id) REFERENCES orders(order_id),
    FOREIGN KEY (product_id) REFERENCES products(product_id)
);
- **创建汇总表**:
CREATE TABLE monthly_product_order_summary (
    month_year CHAR(7),
    product_id INT,
    total_quantity INT,
    total_amount DECIMAL(10, 2),
    PRIMARY KEY (month_year, product_id),
    FOREIGN KEY (product_id) REFERENCES products(product_id)
);
- **填充汇总表数据**:
INSERT INTO monthly_product_order_summary (month_year, product_id, total_quantity, total_amount)
SELECT
    DATE_FORMAT(o.order_date, '%Y-%m') AS month_year,
    op.product_id,
    SUM(op.quantity) AS total_quantity,
    SUM(op.quantity * op.price) AS total_amount
FROM
    orders o
JOIN
    order_products op ON o.order_id = op.order_id
GROUP BY
    DATE_FORMAT(o.order_date, '%Y-%m'),
    op.product_id;

汇总表的维护与监控

  1. 数据一致性检查:定期检查汇总表数据与基础表数据的一致性。可以通过对比基础表和汇总表的聚合结果来实现。例如,计算基础表中某个月的订单总金额,并与汇总表中的对应数据进行比较。
-- 基础表中计算某个月的订单总金额
SELECT SUM(order_amount) AS base_table_total
FROM orders
WHERE DATE_FORMAT(order_date, '%Y-%m') = '2024-01';

-- 汇总表中对应月份的订单总金额
SELECT total_amount AS summary_table_total
FROM monthly_order_summary
WHERE month_year = '2024-01';
  1. 性能监控:使用MySQL的性能监控工具(如SHOW STATUSSHOW GLOBAL STATUS等)来监控汇总表的查询和更新性能。例如,监控Com_insertCom_update等命令的执行次数,以及Handler_read_keyHandler_write等操作的次数。
SHOW STATUS LIKE 'Com_insert';
SHOW STATUS LIKE 'Com_update';
SHOW STATUS LIKE 'Handler_read_key';
SHOW STATUS LIKE 'Handler_write';
  1. 空间使用监控:监控汇总表占用的存储空间,避免因数据增长导致磁盘空间不足。可以使用information_schema库中的TABLES表来获取表的大小信息。
SELECT table_name, data_length + index_length AS total_size
FROM information_schema.TABLES
WHERE table_schema = 'your_database' AND table_name ='monthly_order_summary';

不同存储引擎下的汇总表

  1. InnoDB存储引擎
    • 特点:InnoDB是MySQL默认的存储引擎,支持事务、行级锁,具有较高的数据完整性和并发处理能力。对于汇总表,如果需要保证数据的一致性和支持并发更新,InnoDB是一个不错的选择。
    • 适用场景:适用于对数据一致性要求高,且有并发操作(如多个事务同时更新汇总表)的业务场景,如电商系统的实时销售统计。
  2. MyISAM存储引擎
    • 特点:MyISAM不支持事务,采用表级锁,读写性能较高,但在并发写入方面表现不如InnoDB。它的优势在于存储空间相对较小,查询速度快,尤其是对于只读或读多写少的汇总表。
    • 适用场景:适用于日志分析等场景,这些场景通常对数据一致性要求相对较低,且以查询操作为主,如统计不同类型日志的数量。

汇总表与数据分析工具结合

  1. 与BI工具结合:将汇总表作为数据源接入商业智能(BI)工具,如Tableau、PowerBI等。这些工具可以利用汇总表中的数据进行可视化分析,生成各种报表和图表。
    • 以Tableau为例:在Tableau中连接MySQL数据库,选择汇总表作为数据源,然后通过拖放操作创建柱状图、折线图等可视化图表,展示每月订单金额的变化趋势等信息。
  2. 与数据挖掘工具结合:在数据挖掘中,汇总表可以作为预处理后的数据输入。例如,使用Python的Scikit - learn库进行数据分析时,可以从MySQL汇总表中读取数据,进行分类、聚类等挖掘操作。
import pandas as pd
import mysql.connector

conn = mysql.connector.connect(user='root', password='password', host='127.0.0.1', database='your_database')
query = "SELECT * FROM monthly_order_summary"
df = pd.read_sql(query, conn)
conn.close()

# 进行数据挖掘操作,如聚类分析
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters = 3)
kmeans.fit(df[['total_amount', 'order_count']])
df['cluster'] = kmeans.labels_
print(df)

通过以上详细的介绍和示例,相信你对MySQL汇总表的构建与优化技巧有了全面深入的了解,可以在实际项目中灵活运用这些知识来提高数据库性能和数据处理效率。