文章目录
-
- 每日一句正能量
- 前言
- 目录
- 一、数据可视化的SQL基石:从OLTP到OLAP的思维转换
-
- [1.1 两种SQL范式的本质差异](#1.1 两种SQL范式的本质差异)
- [1.2 最小可行数据管道](#1.2 最小可行数据管道)
- 二、MySQL数据仓库建模实战
-
- [2.1 星型模型设计](#2.1 星型模型设计)
- [2.2 自动化ETL:从业务库到分析库](#2.2 自动化ETL:从业务库到分析库)
- 三、为可视化优化的SQL查询设计
-
- [3.1 时间序列查询模板](#3.1 时间序列查询模板)
- [3.2 漏斗分析查询](#3.2 漏斗分析查询)
- [3.3 预聚合表(物化视图)](#3.3 预聚合表(物化视图))
- 四、Metabase:零代码搭建数据看板
-
- [4.1 Metabase部署与连接](#4.1 Metabase部署与连接)
- [4.2 创建Native Query(原生SQL)卡片](#4.2 创建Native Query(原生SQL)卡片)
- [4.3 动态仪表盘构建](#4.3 动态仪表盘构建)
- [五、Apache Superset:企业级BI平台](#五、Apache Superset:企业级BI平台)
-
- [5.1 Superset与MySQL深度集成](#5.1 Superset与MySQL深度集成)
- [5.2 自定义可视化插件](#5.2 自定义可视化插件)
- 六、Grafana:实时监控与告警
-
- [6.1 MySQL数据源配置](#6.1 MySQL数据源配置)
- [6.2 实时销售监控面板](#6.2 实时销售监控面板)
- [6.3 告警规则配置](#6.3 告警规则配置)
- 七、动态图表:从静态到实时
-
- [7.1 基于MySQL的实时数据推送](#7.1 基于MySQL的实时数据推送)
- [7.2 前端实时图表(ECharts)](#7.2 前端实时图表(ECharts))
- 八、性能优化:当数据量达到千万级
-
- [8.1 分区表设计](#8.1 分区表设计)
- [8.2 列式存储引擎:MyRocks或ClickHouse集成](#8.2 列式存储引擎:MyRocks或ClickHouse集成)
- 九、案例:电商全链路数据看板
-
- [9.1 业务需求拆解](#9.1 业务需求拆解)
- [9.2 完整架构图](#9.2 完整架构图)
- 结语

每日一句正能量
不论做什么事,都要相信你自己,别让别人的一句话将你击倒。人生没有对错,只有选择后的坚持,不后悔,走下去,走着走着,花就开了。
前言
摘要: 数据可视化不是前端工程师的专利。作为后端开发或DBA,你完全可以用SQL+BI工具的组合,快速构建企业级数据看板。本文将带你打通MySQL数据仓库设计、高性能查询优化、到Metabase/Superset/Grafana等开源BI工具落地的完整链路,让数据讲故事。
目录
- 一、数据可视化的SQL基石:从OLTP到OLAP的思维转换
- 二、MySQL数据仓库建模实战
- 三、为可视化优化的SQL查询设计
- 四、Metabase:零代码搭建数据看板
- [五、Apache Superset:企业级BI平台](#五、Apache Superset:企业级BI平台)
- 六、Grafana:实时监控与告警
- 七、动态图表:从静态到实时
- 八、性能优化:当数据量达到千万级
- 九、案例:电商全链路数据看板
一、数据可视化的SQL基石:从OLTP到OLAP的思维转换
1.1 两种SQL范式的本质差异
| 维度 | OLTP(业务系统) | OLAP(可视化分析) |
|---|---|---|
| 查询模式 | 单条记录增删改查 | 大批量聚合统计 |
| 数据范式 | 严格3NF,避免冗余 | 适度反范化,预聚合 |
| 索引策略 | B+树主键+二级索引 | 位图索引、列式存储 |
| 时间维度 | 当前状态 | 历史趋势、同比环比 |
| 典型查询 | SELECT * FROM orders WHERE id=123 |
SELECT DATE(created_at), SUM(amount) FROM orders GROUP BY 1 |
关键洞察: 直接在业务库执行复杂聚合查询,会导致锁竞争、CPU飙升、甚至OOM。数据可视化需要独立的分析库。
1.2 最小可行数据管道
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 业务MySQL │────▶│ ETL工具 │────▶│ 分析MySQL │────▶│ BI工具 │
│ (OLTP) │ │ (Airflow/ │ │ (OLAP) │ │ (Metabase/ │
│ │ │ Canal/ │ │ │ │ Superset) │
│ 实时交易 │ │ DataX) │ │ 预聚合数据 │ │ │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
二、MySQL数据仓库建模实战
2.1 星型模型设计
以电商订单分析为例,构建星型模型:
sql
-- 事实表:订单事实
CREATE TABLE fact_orders (
order_id BIGINT PRIMARY KEY,
order_date_key INT NOT NULL, -- 外键关联日期维表
customer_key INT NOT NULL, -- 外键关联客户维表
product_key INT NOT NULL, -- 外键关联产品维表
region_key INT NOT NULL, -- 外键关联地区维表
-- 度量值(可加)
quantity INT NOT NULL,
unit_price DECIMAL(10,2) NOT NULL,
discount_amount DECIMAL(10,2) DEFAULT 0,
shipping_fee DECIMAL(10,2) DEFAULT 0,
-- 派生度量
gross_amount DECIMAL(10,2) AS (quantity * unit_price) STORED,
net_amount DECIMAL(10,2) AS (quantity * unit_price - discount_amount) STORED,
-- 元数据
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_date (order_date_key),
INDEX idx_customer (customer_key),
INDEX idx_product (product_key)
) ENGINE=InnoDB;
-- 维度表:日期维度(预生成10年数据)
CREATE TABLE dim_date (
date_key INT PRIMARY KEY, -- 格式:YYYYMMDD
full_date DATE NOT NULL,
year SMALLINT NOT NULL,
quarter TINYINT NOT NULL,
month TINYINT NOT NULL,
day TINYINT NOT NULL,
week_of_year TINYINT NOT NULL,
day_of_week TINYINT NOT NULL, -- 1=周一, 7=周日
is_weekend BOOLEAN GENERATED ALWAYS AS (day_of_week IN (6,7)) STORED,
is_holiday BOOLEAN DEFAULT FALSE, -- 需手动维护节假日
INDEX idx_year_month (year, month)
) ENGINE=InnoDB;
-- 维度表:客户维度(SCD Type 2缓慢变化维)
CREATE TABLE dim_customer (
customer_key INT AUTO_INCREMENT PRIMARY KEY,
customer_id VARCHAR(50) NOT NULL, -- 业务主键
customer_name VARCHAR(100) NOT NULL,
customer_level ENUM('普通','银卡','金卡','钻石') DEFAULT '普通',
region VARCHAR(50),
-- SCD Type 2 字段
effective_date DATE NOT NULL,
expiry_date DATE DEFAULT '9999-12-31',
is_current BOOLEAN DEFAULT TRUE,
INDEX idx_customer_id (customer_id, is_current)
) ENGINE=InnoDB;
2.2 自动化ETL:从业务库到分析库
sql
-- 存储过程:每日增量同步
DELIMITER //
CREATE PROCEDURE sp_sync_orders(IN sync_date DATE)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
INSERT INTO etl_log (table_name, sync_date, status, error_msg)
VALUES ('fact_orders', sync_date, 'FAILED', 'Transaction rolled back');
END;
START TRANSACTION;
-- 删除已存在的分区数据(支持重跑)
DELETE FROM fact_orders
WHERE order_date_key = DATE_FORMAT(sync_date, '%Y%m%d');
-- 增量插入
INSERT INTO fact_orders (
order_id, order_date_key, customer_key, product_key, region_key,
quantity, unit_price, discount_amount, shipping_fee
)
SELECT
o.order_id,
DATE_FORMAT(o.created_at, '%Y%m%d'),
dc.customer_key,
dp.product_key,
dr.region_key,
o.quantity,
o.unit_price,
o.discount_amount,
o.shipping_fee
FROM oltp.orders o
JOIN dim_customer dc ON o.customer_id = dc.customer_id AND dc.is_current = TRUE
JOIN dim_product dp ON o.product_id = dp.product_id AND dp.is_current = TRUE
JOIN dim_region dr ON o.region_code = dr.region_code
WHERE DATE(o.created_at) = sync_date;
COMMIT;
INSERT INTO etl_log (table_name, sync_date, status, rows_affected)
VALUES ('fact_orders', sync_date, 'SUCCESS', ROW_COUNT());
END //
DELIMITER ;
-- 定时任务(Event Scheduler)
CREATE EVENT evt_daily_sync
ON SCHEDULE EVERY 1 DAY STARTS '2024-01-01 02:00:00'
DO CALL sp_sync_orders(CURDATE() - INTERVAL 1 DAY);
三、为可视化优化的SQL查询设计
3.1 时间序列查询模板
BI工具最常用的查询模式,需要精心设计索引:
sql
-- 日销售趋势(支持同比环比)
SELECT
d.full_date,
SUM(f.net_amount) as daily_sales,
COUNT(DISTINCT f.order_id) as order_count,
AVG(f.net_amount) as avg_order_value,
-- 同比(去年同期)
LAG(SUM(f.net_amount), 365) OVER (ORDER BY d.full_date) as sales_yoy,
-- 环比(上周同日)
LAG(SUM(f.net_amount), 7) OVER (ORDER BY d.full_date) as sales_wow,
-- 7日移动平均
AVG(SUM(f.net_amount)) OVER (
ORDER BY d.full_date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) as ma7_sales
FROM fact_orders f
JOIN dim_date d ON f.order_date_key = d.date_key
WHERE d.full_date BETWEEN '2023-01-01' AND '2024-01-01'
GROUP BY d.full_date, d.date_key
ORDER BY d.full_date;
-- 关键索引
CREATE INDEX idx_fact_orders_date_amount ON fact_orders(order_date_key, net_amount);
3.2 漏斗分析查询
sql
-- 用户行为漏斗:访问->加购->下单->支付
WITH funnel_stages AS (
SELECT
user_id,
session_date,
MAX(CASE WHEN event_type = 'page_view' THEN 1 ELSE 0 END) as has_view,
MAX(CASE WHEN event_type = 'add_cart' THEN 1 ELSE 0 END) as has_cart,
MAX(CASE WHEN event_type = 'create_order' THEN 1 ELSE 0 END) as has_order,
MAX(CASE WHEN event_type = 'pay_success' THEN 1 ELSE 0 END) as has_pay
FROM user_events
WHERE session_date BETWEEN @start_date AND @end_date
GROUP BY user_id, session_date
)
SELECT
'访问' as stage,
COUNT(*) as user_count,
100.0 as conversion_rate
FROM funnel_stages WHERE has_view = 1
UNION ALL
SELECT
'加购',
COUNT(*),
COUNT(*) * 100.0 / (SELECT COUNT(*) FROM funnel_stages WHERE has_view = 1)
FROM funnel_stages WHERE has_cart = 1
UNION ALL
SELECT
'下单',
COUNT(*),
COUNT(*) * 100.0 / (SELECT COUNT(*) FROM funnel_stages WHERE has_view = 1)
FROM funnel_stages WHERE has_order = 1
UNION ALL
SELECT
'支付',
COUNT(*),
COUNT(*) * 100.0 / (SELECT COUNT(*) FROM funnel_stages WHERE has_view = 1)
FROM funnel_stages WHERE has_pay = 1;
3.3 预聚合表(物化视图)
对于高频查询的大表,使用预聚合提升性能:
sql
-- 日粒度预聚合表
CREATE TABLE agg_daily_sales AS
SELECT
order_date_key,
region_key,
product_category_key,
COUNT(*) as order_count,
SUM(quantity) as total_quantity,
SUM(gross_amount) as gross_sales,
SUM(net_amount) as net_sales,
SUM(discount_amount) as total_discount,
COUNT(DISTINCT customer_key) as unique_customers
FROM fact_orders
GROUP BY order_date_key, region_key, product_category_key;
-- 实时刷新(MySQL 8.0.13+支持原子性DDL)
CREATE OR REPLACE TABLE agg_daily_sales AS ...;
-- 或者使用触发器保持同步(适合近实时场景)
DELIMITER //
CREATE TRIGGER trg_orders_agg_insert
AFTER INSERT ON fact_orders
FOR EACH ROW
BEGIN
INSERT INTO agg_daily_sales (
order_date_key, region_key, product_category_key,
order_count, total_quantity, gross_sales, net_sales, total_discount, unique_customers
) VALUES (
NEW.order_date_key, NEW.region_key, NEW.product_category_key,
1, NEW.quantity, NEW.gross_amount, NEW.net_amount, NEW.discount_amount, 1
)
ON DUPLICATE KEY UPDATE
order_count = order_count + 1,
total_quantity = total_quantity + NEW.quantity,
gross_sales = gross_sales + NEW.gross_amount,
net_sales = net_sales + NEW.net_amount,
total_discount = total_discount + NEW.discount_amount,
unique_customers = unique_customers + IF(
(SELECT COUNT(*) FROM fact_orders
WHERE order_date_key=NEW.order_date_key
AND customer_key=NEW.customer_key) = 1, 1, 0
);
END //
DELIMITER ;
四、Metabase:零代码搭建数据看板
4.1 Metabase部署与连接
yaml
# docker-compose.yml
version: '3'
services:
metabase:
image: metabase/metabase:latest
ports:
- "3000:3000"
environment:
- MB_DB_TYPE=mysql
- MB_DB_DBNAME=metabase
- MB_DB_PORT=3306
- MB_DB_USER=metabase
- MB_DB_PASS=secret
- MB_DB_HOST=mysql-analytics
volumes:
- metabase-data:/metabase-data
mysql-analytics:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=metabase
volumes:
- mysql-data:/var/lib/mysql
- ./analytics_dump.sql:/docker-entrypoint-initdb.d/init.sql
volumes:
metabase-data:
mysql-data:
4.2 创建Native Query(原生SQL)卡片
Metabase支持将SQL查询直接转为图表:
sql
-- 保存为Metabase Question,选择"Line"图表类型
-- 变量语法支持动态筛选
SELECT
{{date_column}} as date,
region,
SUM(net_amount) as sales
FROM fact_orders
WHERE {{date_column}} BETWEEN {{start_date}} AND {{end_date}}
[[AND region = {{selected_region}}]]
GROUP BY 1, 2
ORDER BY 1;
Metabase变量语法:
{``{variable}}:必填变量[[AND column = {``{variable}}]]:可选条件(变量为空时整段消失){``{date_column}}:字段选择器,允许用户选择时间维度(日/周/月)
4.3 动态仪表盘构建
javascript
// Metabase嵌入式仪表盘配置示例
// 在前端应用中集成iframe
const metabaseUrl = "http://localhost:3000";
const dashboardId = 123;
const token = generateSignedToken({ // 使用Metabase嵌入SDK生成
resource: { dashboard: dashboardId },
params: {
"region": "华东", // 预筛选参数
"start_date": "2024-01-01"
},
exp: Math.round(Date.now() / 1000) + (10 * 60) // 10分钟过期
});
const iframeUrl = `${metabaseUrl}/embed/dashboard/${token}#bordered=true&titled=true`;
// 嵌入到React/Vue组件中
<iframe src={iframeUrl} width="100%" height="800" frameBorder="0"></iframe>
五、Apache Superset:企业级BI平台
5.1 Superset与MySQL深度集成
python
# superset_config.py
# 配置MySQL作为元数据库和查询引擎
SQLALCHEMY_DATABASE_URI = 'mysql+mysqlconnector://superset:password@localhost/superset_metadata'
# 添加MySQL数据源
from superset.connectors.sqla.models import SqlaTable
from superset import db
# 通过API或UI添加数据库
database = Database(
database_name='analytics_warehouse',
sqlalchemy_uri='mysql+mysqlconnector://readonly:password@analytics-host:3306/analytics_db',
extra=json.dumps({
"metadata_params": {},
"engine_params": {
"connect_args": {
"ssl_disabled": True,
"autocommit": True
}
},
"metadata_cache_timeout": {
"schema_cache_timeout": 300,
"table_cache_timeout": 600
}
})
)
db.session.add(database)
db.session.commit()
5.2 自定义可视化插件
typescript
// 开发Superset自定义图表:桑基图(用户流转分析)
// plugin-chart-sankey/src/SankeyChart.tsx
import React from 'react';
import { Sankey } from '@ant-design/charts';
import { ChartProps } from '@superset-ui/core';
interface SankeyData {
source: string;
target: string;
value: number;
}
export default function SankeyChart(props: ChartProps<SankeyData[]>) {
const { data, width, height } = props;
const config = {
data: data.map(d => ({ source: d.source, target: d.target, value: d.value })),
sourceField: 'source',
targetField: 'target',
weightField: 'value',
nodeWidthRatio: 0.02,
nodePaddingRatio: 0.03,
width,
height,
tooltip: {
formatter: (datum: any) => ({
name: `${datum.source} → ${datum.target}`,
value: datum.value,
}),
},
};
return <Sankey {...config} />;
}
// 对应SQL查询
/*
SELECT
'首页' as source,
CASE
WHEN page = 'product_list' THEN '商品列表'
WHEN page = 'search_result' THEN '搜索结果'
ELSE '其他'
END as target,
COUNT(*) as value
FROM user_behavior
WHERE event_date = '2024-01-01'
GROUP BY 1, 2;
*/
六、Grafana:实时监控与告警
6.1 MySQL数据源配置
yaml
# grafana-datasources.yml
apiVersion: 1
datasources:
- name: MySQL-Analytics
type: mysql
url: analytics-db:3306
database: analytics_db
user: grafana_reader
secureJsonData:
password: ${MYSQL_PASSWORD}
jsonData:
maxOpenConns: 100
maxIdleConns: 100
connMaxLifetime: 14400
6.2 实时销售监控面板
sql
-- Grafana Query A:实时销售额(5分钟粒度)
SELECT
UNIX_TIMESTAMP(DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:00')) as time_sec,
SUM(net_amount) as value,
'销售额' as metric
FROM fact_orders
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 6 HOUR)
GROUP BY UNIX_TIMESTAMP(DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:00'))
ORDER BY time_sec;
-- Grafana Query B:实时订单量
SELECT
UNIX_TIMESTAMP(DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:00')) as time_sec,
COUNT(*) as value,
'订单量' as metric
FROM fact_orders
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 6 HOUR)
GROUP BY UNIX_TIMESTAMP(DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:00'))
ORDER BY time_sec;
6.3 告警规则配置
yaml
# grafana-alerts.yml
apiVersion: 1
groups:
- orgId: 1
name: sales_alerts
folder: Business Metrics
interval: 60s
rules:
- uid: sales_drop_alert
title: 销售额骤降告警
condition: C
data:
- refId: A
relativeTimeRange:
from: 300
to: 0
datasourceUid: mysql-analytics
model:
format: time_series
rawSql: |
SELECT
NOW() as time,
SUM(net_amount) as current_sales
FROM fact_orders
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)
- refId: B
relativeTimeRange:
from: 600
to: 300
datasourceUid: mysql-analytics
model:
format: time_series
rawSql: |
SELECT
NOW() as time,
SUM(net_amount) as previous_sales
FROM fact_orders
WHERE created_at BETWEEN DATE_SUB(NOW(), INTERVAL 10 MINUTE)
AND DATE_SUB(NOW(), INTERVAL 5 MINUTE)
- refId: C
datasourceUid: __expr__
model:
type: threshold
expression: 'A / B < 0.7' # 当前销售额低于上期70%触发
noDataState: NoData
execErrState: Error
for: 5m
annotations:
summary: "销售额较5分钟前下降超过30%"
description: "当前销售额: {{ $values.A }}, 上期: {{ $values.B }}"
labels:
severity: critical
七、动态图表:从静态到实时
7.1 基于MySQL的实时数据推送
python
# Python + Flask-SocketIO + MySQL实现实时数据推送
# 替代方案:使用Apache Kafka + Flink + MySQL CDC
from flask import Flask
from flask_socketio import SocketIO, emit
import pymysql
import threading
import time
app = Flask(__name__)
socketio = SocketIO(app, cors_allowed_origins="*")
class MySQLWatcher:
def __init__(self):
self.conn = pymysql.connect(
host='localhost',
user='realtime',
password='secret',
database='analytics_db',
cursorclass=pymysql.cursors.DictCursor
)
self.last_id = 0
def watch_orders(self):
"""监控新订单并推送"""
while True:
with self.conn.cursor() as cursor:
sql = """
SELECT order_id, net_amount, created_at
FROM fact_orders
WHERE order_id > %s
ORDER BY order_id
LIMIT 100
"""
cursor.execute(sql, (self.last_id,))
new_orders = cursor.fetchall()
if new_orders:
self.last_id = new_orders[-1]['order_id']
# 计算实时指标
total_sales = sum(o['net_amount'] for o in new_orders)
# 推送到前端
socketio.emit('sales_update', {
'new_orders': len(new_orders),
'sales_amount': float(total_sales),
'timestamp': time.time()
}, broadcast=True)
time.sleep(1) # 每秒轮询(生产环境应使用Binlog监听)
watcher = MySQLWatcher()
@socketio.on('connect')
def handle_connect():
print('Client connected')
emit('init_data', {'status': 'connected'})
if __name__ == '__main__':
# 启动监控线程
t = threading.Thread(target=watcher.watch_orders)
t.daemon = True
t.start()
socketio.run(app, host='0.0.0.0', port=5000)
7.2 前端实时图表(ECharts)
html
<!-- 实时销售监控页面 -->
<!DOCTYPE html>
<html>
<head>
<title>实时销售监控</title>
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
</head>
<body>
<div id="main" style="width: 100%; height: 600px;"></div>
<script>
const socket = io('http://localhost:5000');
const chart = echarts.init(document.getElementById('main'));
// 初始化空数据
const data = [];
const now = new Date();
for (let i = 0; i < 60; i++) {
data.push({
name: new Date(now - (60 - i) * 1000).toString(),
value: [new Date(now - (60 - i) * 1000), 0]
});
}
const option = {
title: { text: '实时销售额(元/秒)' },
tooltip: { trigger: 'axis' },
xAxis: { type: 'time', splitLine: { show: false } },
yAxis: { type: 'value', splitLine: { show: true } },
series: [{
name: '销售额',
type: 'line',
smooth: true,
data: data,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgb(255, 158, 68)' },
{ offset: 1, color: 'rgb(255, 70, 131)' }
])
}
}]
};
chart.setOption(option);
// 接收实时数据
socket.on('sales_update', function(msg) {
const now = new Date();
data.shift();
data.push({
name: now.toString(),
value: [now, msg.sales_amount]
});
chart.setOption({ series: [{ data: data }] });
});
</script>
</body>
</html>
八、性能优化:当数据量达到千万级
8.1 分区表设计
sql
-- 按时间范围分区(MySQL 8.0)
CREATE TABLE fact_orders_partitioned (
order_id BIGINT,
order_date_key INT,
-- ... 其他字段
PRIMARY KEY (order_id, order_date_key) -- 分区键必须包含在主键中
) PARTITION BY RANGE (order_date_key) (
PARTITION p202301 VALUES LESS THAN (20230200),
PARTITION p202302 VALUES LESS THAN (20230300),
PARTITION p202303 VALUES LESS THAN (20230400),
-- ...
PARTITION p_future VALUES LESS THAN MAXVALUE
);
-- 查询优化器自动分区裁剪
EXPLAIN PARTITIONS
SELECT * FROM fact_orders_partitioned
WHERE order_date_key BETWEEN 20230101 AND 20230131;
-- 结果:只扫描p202301分区
8.2 列式存储引擎:MyRocks或ClickHouse集成
sql
-- 对于纯分析场景,使用ClickHouse作为MySQL的从库
-- MySQL主库 -> Canal -> Kafka -> ClickHouse
-- 在ClickHouse中创建MySQL引擎表(实时查询MySQL数据)
CREATE TABLE mysql_orders (
order_id UInt64,
order_date Date,
net_amount Decimal(10,2)
) ENGINE = MySQL('mysql-host:3306', 'analytics_db', 'fact_orders', 'readonly', 'password');
-- 本地物化视图加速查询
CREATE MATERIALIZED VIEW mv_daily_sales
ENGINE = SummingMergeTree()
ORDER BY (order_date)
AS SELECT
order_date,
sum(net_amount) as total_sales,
count() as order_count
FROM mysql_orders
GROUP BY order_date;
九、案例:电商全链路数据看板
9.1 业务需求拆解
| 看板模块 | 数据来源 | 刷新频率 | 技术方案 |
|---|---|---|---|
| 实时销售大屏 | fact_orders | 1秒 | MySQL+WebSocket+ECharts |
| 用户行为漏斗 | user_events | 5分钟 | Superset桑基图 |
| 库存预警 | inventory_snapshot | 实时 | Grafana+告警 |
| 营销ROI分析 | ad_spend + orders | 1小时 | Metabase自助分析 |
9.2 完整架构图
┌─────────────────────────────────────────────────────────────┐
│ 前端展示层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 实时大屏 │ │ Metabase │ │ Superset │ │ Grafana │ │
│ │ (WebSocket)│ │ (自助BI) │ │ (高级可视化)│ │ (监控告警)│ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
└───────┼────────────┼────────────┼────────────┼───────────┘
│ │ │ │
└────────────┴────────────┴────────────┘
│
┌─────┴─────┐
│ API网关 │ ← 统一查询接口/权限控制
│ (GraphQL) │
└─────┬─────┘
│
┌─────────────────┼─────────────────┐
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ MySQL │ │ClickHouse│ │ Redis │
│ (热数据) │ │ (冷分析) │ │ (缓存) │
│ 7天数据 │ │ 历史数据 │ │ 实时指标 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└─────────────────┼─────────────────┘
│
┌─────┴─────┐
│ ETL管道 │
│(Airflow+ │
│ Canal+ │
│ Flink) │
└─────┬─────┘
│
┌─────────────────┼─────────────────┐
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ 业务MySQL│ │ 日志系统 │ │ 第三方API│
│ (订单/用户)│ │ (Kafka) │ │ (广告/支付)│
└─────────┘ └─────────┘ └─────────┘
结语
数据可视化不是简单的"SQL出数+图表展示",而是数据工程、性能优化、前端技术的交叉领域。MySQL作为最熟悉的关系型数据库,通过合理的数仓建模、预聚合策略和BI工具集成,完全能够支撑从实时大屏到深度分析的全场景需求。
关键成功要素:
- 数据模型先行:星型模型、预聚合表、分区策略
- 工具选型匹配:Metabase适合快速探索,Superset适合企业级BI,Grafana适合监控告警
- 性能分层:热数据MySQL、冷数据ClickHouse、缓存Redis
- 实时性分级:秒级WebSocket、分钟级ETL、小时级离线分析
掌握这套技术栈,后端工程师也能成为数据产品的主人。
附录:工具版本参考
- MySQL 8.0.32+
- Metabase v0.47+
- Apache Superset 3.0+
- Grafana 10.0+
转载自:https://blog.csdn.net/u014727709/article/details/157909169
欢迎 👍点赞✍评论⭐收藏,欢迎指正