1. 选这套组合的原因:Superset 做可视化,Sakila 当练习数据,compose 管部署
先说一下我为什么对这套组合这么上心。过去两年里,我先后在好几个项目里负责过数据看板的搭建,从最初的直接用代码拼图表,到后来换成 BI 工具,走了不少弯路。Superset 是我用的比较顺手的一个开源 BI 工具,但每次在一台新机器上部署它,总会遇到各种奇怪的依赖问题——Python 版本不对、数据库驱动缺失、前端资源构建失败等等。这套流程走多了,我逐渐意识到:与其每次都手动折腾环境,不如一开始就把整个部署过程编排好。这也是我这次决定用 docker compose 把 Superset 和 MySQL 一起拉起来的原因。
这个方案能解决什么问题?简单说,就是你只需要在服务器上装好 Docker 和 Compose 插件,然后执行一条命令,Superset 和 MySQL 就能同时跑起来,并且它们之间已经通过内部网络打通了。数据层面,我选的是 MySQL 官方的 Sakila 示例数据库。这个库模拟了一个 DVD 租赁店的业务场景,里面有 film(电影)、actor(演员)、customer(客户)、rental(租赁记录)等十几张表,数据之间有完整的外键关联。相比那些只有两三张表的入门示例,用 Sakila 才能练到真正的多表 join 和聚合分析,比如"哪个演员的电影被租赁次数最多""每个月的租赁收入趋势"这类问题,正好匹配 Superset 的核心使用场景。
这篇内容适合谁看?如果你刚接触 Superset,想快速在本地搭一套环境看看效果;或者你已经用过一些 BI 工具,但一直没有找到一个干净利落的部署方式;再或者你单纯想拿一份真实度足够高的数据来练习 SQL 和图表设计——这套组合都值得一试。我下面写的所有内容,都是我实际执行过的步骤,命令和配置会直接贴出来,你可以照着跑。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开工前的环境检查与架构规划
2.1 需要准备的基础环境
先说结论:整个部署过程不需要你自己装 MySQL,也不用配 Python 环境,但有一个前提——你的机器上得有 Docker。我建议 Docker Engine 版本不低于 20.10,因为新版 Docker 已经默认集成了 compose 子命令,也就是你敲 docker compose 而不是 docker-compose。如果你还是老版本的 Docker,先去把引擎升上去,这能省掉后面非常多的兼容性麻烦。
然后确认一下两个版本:
bash复制docker --version
docker compose version
我这边的环境是 Docker 27.4.2,Compose 2.32.2,跑下面整套流程没有任何问题。如果你是 Ubuntu、CentOS、macOS 或者 Windows 的 Docker Desktop 环境,操作基本一致。唯一要注意的是,如果你在 Windows 上用 WSL2 跑 Docker,注意把项目目录放在 Linux 文件系统内,否则挂载卷的性能会有明显损耗。
2.2 网络拓扑与数据流转路径
在写 docker-compose.yml 之前,我先画了一条数据流转的线路(其实就是服务之间的调用关系),理清这条线之后,后面的配置会顺畅很多:
- Superset 容器负责提供 Web 界面和图表渲染服务,监听 8088 端口;
- MySQL 容器负责存储 Sakila 的数据,监听 3306 端口;
- 在 Compose 内部网络中,Superset 可以通过服务名
mysql直接访问 MySQL 的 3306 端口,不需要走宿主机 IP; - 宿主机只需要暴露 Superset 的 8088 端口给你访问,MySQL 的 3306 端口可以留给你自己的数据库客户端连接,也可以不暴露。
这个设计的核心是把服务间的通信限制在 Compose 创建的内部网络里,对外只暴露必要的端口。好处很明显:一是安全,MySQL 不会直接被外部网络扫描到;二是配置简单,你不需要关心宿主机 IP 是什么,mysql 这个服务名在容器内就等效于一个稳定的 DNS 记录。
2.3 持久化方案:别让数据跟着容器消失
容器是无状态的,这意味着如果哪天你不小心执行了 docker compose down,默认情况下 MySQL 里的数据会全部清零。为了避免这种事故,我在 compose 文件里用到了两种持久化手段:
- 命名的 volume:用于存 MySQL 的数据文件,这是最推荐的方式,数据由 Docker 统一管理;
- bind mount:用于把宿主机上的初始化 SQL 脚本挂载到 MySQL 容器的
/docker-entrypoint-initdb.d/目录,容器首次启动时会自动执行该目录下的 SQL 脚本。
Superset 这边,它自己的元数据(用户、看板配置、图表配置等)默认存在一个内置的 SQLite 数据库文件里。我同样用命名 volume 把它持久化下来,这样容器重启后你配置好的图表不会丢。
3. 先搞定 MySQL:拿 Sakila 当试验田
3.1 获取 Sakila 初始化脚本
Sakila 是 MySQL 官方提供的示例数据库,源码托管在 GitHub 的 mysql/mysql-server 仓库里,也可以从 dev.mysql.com 的文档区下载。我习惯直接把 SQL 文件下下来,因为这样不需要一路点网页。
打开终端,先创建项目目录,然后拉取脚本:
bash复制mkdir -p ~/superset-sakila/mysql-init
cd ~/superset-sakila/mysql-init
curl -LO https://raw.githubusercontent.com/mysql/mysql-server/8.0/share/sakila/sakila-schema.sql
curl -LO https://raw.githubusercontent.com/mysql/mysql-server/8.0/share/sakila/sakila-data.sql
下载完看一眼确认文件没问题:
bash复制ls -lh
head -n 30 sakila-schema.sql
sakila-schema.sql 负责建库建表,sakila-data.sql 负责灌数据。这里有一个细节:MySQL 官方仓库里的 Sakila 脚本路径可能会随分支变化,如果上面这条 URL 失效了,可以打开 GitHub 的 mysql/mysql-server 仓库,在 share/sakila/ 目录下找对应版本的文件。另一个可选的来源是 jOOQ/sakila 仓库,它维护了多个数据库方言的 Sakila 版本,MySQL 的也能用。
3.2 用 docker compose 启动 MySQL 服务
我直接用了 MySQL 8.0 的官方镜像,生产环境这么久跑下来,8.0 无论是稳定性还是 SQL 功能都比较成熟。Compose 文件里的 MySQL 服务我这样定义:
yaml复制 mysql:
image: mysql:8.0
container_name: sakila-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: sakila
volumes:
- mysql_data:/var/lib/mysql
- ./mysql-init:/docker-entrypoint-initdb.d:ro
ports:
- "3306:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-prootpass"]
interval: 5s
timeout: 5s
retries: 20
几个地方的用意我说明一下:
MYSQL_DATABASE: sakila:镜像首次初始化时会自动创建一个名为 sakila 的空库;/docker-entrypoint-initdb.d:镜像官方支持的初始化脚本目录。MySQL 容器第一次启动时,会按文件名的字母顺序执行该目录下的.sh、.sql和.sql.gz文件。sakila-schema.sql里虽然写了CREATE DATABASE sakila,它和MYSQL_DATABASE的配置不冲突,脚本执行时会自动 USE 到 sakila 库;- healthcheck:这个很重要。Superset 依赖 MySQL,如果 MySQL 还没就绪 Superset 就尝试连接,会直接报错退出。通过 healthcheck 可以让 Compose 感知 MySQL 的真实状态;
container_name我显式指定了,方便后面用docker exec进入容器排错。
3.3 初始化过程执行说明
写好 compose 文件后(完整版我放到下一节再给),先只启动 MySQL 服务,观察初始化日志:
bash复制docker compose up -d mysql
docker compose logs -f mysql
日志里出现 ready for connections 就说明 MySQL 启动成功。然后进入容器验证 Sakila 是否导入成功:
bash复制docker exec -it sakila-mysql mysql -uroot -prootpass -e "USE sakila; SHOW TABLES; SELECT COUNT(*) FROM film;"
正常情况下你应该看到 23 张表,film 表里有 1000 条记录。
这一步最容易出的问题有两个:
- 如果你之前已经在同一个目录跑过
docker compose up,MySQL 的数据卷已经初始化过,之后你再往mysql-init目录加 SQL 文件,容器不会重新执行——初始化脚本只在数据卷首次创建时生效。解决办法是把对应的命名 volume 删掉重建:docker compose down -v,注意这会清除所有数据; - 如果挂载了旧版本的 sakila-schema.sql,某些 8.0 版本会报
ERROR 1067 (42000): Invalid default value for 'last_update',这是因为脚本里的 timestamp 默认值写法比较老。遇到这种情况,下载我在上面链接里指定的 8.0 分支脚本,或者手动搜索脚本里的0000-00-00相关的默认值并改成合法值。
4. 编排 Superset:docker-compose.yml 怎么写得又稳又方便调试
4.1 镜像选型:apache/superset 到底用哪个 tag
Superset 官方镜像在 Docker Hub 上有多个 tag,从 apache/superset:3.1.0 到 apache/superset:4.1.1 都是稳定版本。对新手来说,我的建议是直接用带具体版本号的 tag,不要用 latest。原因很简单:latest 会随上游更新变化,某天你重新拉镜像时可能就升级到大版本了,配置可能会有兼容性问题,而 BI 工具这类东西,稳定压倒一切。我这套示例用的 tag 是 apache/superset:4.1.1,这是 4.x 系列里我实测比较稳的版本。
补充一点:如果你网络环境拉 Docker Hub 镜像比较慢,可以给 Docker 配置 registry mirror,或者直接用加速器。这属于基础运维操作,这里不展开。
4.2 完整 compose 文件解读
下面是整套部署的 docker-compose.yml,我把 Superset 和 MySQL 都写在一个文件里:
yaml复制services:
mysql:
image: mysql:8.0
container_name: sakila-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: sakila
volumes:
- mysql_data:/var/lib/mysql
- ./mysql-init:/docker-entrypoint-initdb.d:ro
ports:
- "3306:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-prootpass"]
interval: 5s
timeout: 5s
retries: 20
superset:
image: apache/superset:4.1.1
container_name: superset-app
restart: unless-stopped
environment:
SUPERSET_SECRET_KEY: 'please-change-me-to-a-random-secret'
SUPERSET_LOAD_EXAMPLES: 'no'
ports:
- "8088:8088"
volumes:
- superset_data:/app/superset_home
depends_on:
mysql:
condition: service_healthy
volumes:
mysql_data:
superset_data:
这里有几个关键决策点,我拆开讲:
第一,depends_on 的写法。传统 Compose 文件的 depends_on 只控制启动顺序,不管服务是否真正就绪。从 Compose 2.20 版本开始支持了 condition: service_healthy 这种健康状态依赖,意思很明确:MySQL 的 healthcheck 通过之前,Superset 容器不会启动。这样就不会出现 Superset 先启动、连接 MySQL 失败然后整个应用崩掉的局面。
第二,SUPERSET_SECRET_KEY。这个变量用于加密 Superset 的会话 cookie 和签名凭证。官方镜像里如果不设置,Superset 会默认用一个固定的弱 key,生产环境有安全风险。你可以用下面命令生成一个随机字符串填进去:
bash复制openssl rand -base64 42
第三,SUPERSET_LOAD_EXAMPLES 我设成了 no。官方镜像启动时会通过一个初始化脚本决定是否加载内置示例看板,示例数据会占用额外的初始化时间,而且我们这次要接的是 Sakila 数据,内置示例反而会干扰界面。不过要注意,这个环境变量只影响首次初始化,如果你后续想让 Superset 加载示例,可以再手动执行 superset load_examples。
第四,Superset 的 volume 挂载点为什么是 /app/superset_home。官方镜像的说明文档里明确写了,Superset 的元数据库(默认 SQLite 文件)、上传文件缓存等都放在这个目录下。把它持久化到命名 volume,意味着你辛辛苦苦配置的看板、图表、数据源连接,在容器重建后都能恢复。
4.3 先启动所有服务,验证依赖关系
写好后执行:
bash复制docker compose up -d
docker compose ps
这时你会看到两个容器都在运行。如果一切正常,Superset 的依赖等待逻辑会先等 MySQL 健康检查通过,再进入启动状态。你可以观察日志确认:
bash复制docker compose logs -f superset
看到 WARNING: Superset is running in development mode 或者 App is running on http://0.0.0.0:8088 之类的输出,就说明 Superset 已经起来了。此时浏览器访问 http://localhost:8088,应该能看到登录页面,但你还进不去,因为还没有初始化管理员账号。
5. 初始化 Superset:从空容器到能登录后台
5.1 三条必须执行的初始化命令
Superset 官方镜像自带的镜像入口脚本已经做了一部分初始化工作,比如创建默认的 admin / admin 账号,但不同版本行为有差异。我习惯手动执行一遍完整的初始化流程,确保账号是自己可控的。具体命令如下:
bash复制# 第一步:升级 Superset 的元数据库
docker compose exec superset superset db upgrade
# 第二步:创建管理员账号
docker compose exec superset superset fab create-admin \
--username admin \
--firstname Admin \
--lastname User \
--email admin@example.com \
--password admin
# 第三步:初始化角色和权限
docker compose exec superset superset init
db upgrade 会执行所有数据库迁移脚本,把 Superset 内部的表结构建好。fab create-admin 是 Flask-AppBuilder 提供的命令行工具,用来创建管理员用户。superset init 则会创建默认的角色、权限和视图,这步不做的话,后面登录进去各种按钮可能显示不全。
三条命令执行完毕,回到浏览器刷新 http://localhost:8088,用刚才创建的用户名密码登录。正常情况下你会进入 Superset 的主界面,左侧菜单有 Dashboards、Charts、Datasets、SQL Lab 等入口。
5.2 登录后应该先干的三件事
进入主界面后,别急着连数据库,先把下面三件事做了,能省掉后面一堆麻烦:
第一,右上角头像进入 Settings -> User profile,确认当前用户的语言、时区。把时区设置成你本地的时区,否则后面图表的时间轴显示会差好几个小时,很难排查。
第二,到 Settings -> Analytics 界面(有的版本叫 Database Actions),确认 Superset 内置的 SQLite 数据库已经被自动扫描到。这一步其实不用手动操作,主要是让你了解数据库列表长什么样,为后面添加 MySQL 连接做铺垫。
第三,如果你打算用 SQL Lab 做临时查询,到 Settings -> SQL Lab 配置里调整一下查询超时时间,默认有可能太短,复杂查询会被直接杀掉。
这三件事都不复杂,但是能明显提升后续的使用体验。
6. 数据库连接配置:让 Superset 真正读到 Sakila 的表
6.1 装 MySQL 驱动:一个常见的隐藏坑
现在到了最关键的一步:让 Superset 能连上 MySQL。Superset 后端连接数据库依赖 Python 的 DB-API 驱动,不同数据库需要不同的驱动包。官方镜像默认预装了 PostgreSQL 的驱动,但 MySQL 驱动需要你自己装。
执行下面这条命令安装驱动:
bash复制docker compose exec superset pip install mysqlclient
这里有两个方案可以选:mysqlclient 或者 pymysql。我推荐 mysqlclient,因为它是 C 扩展实现,性能更好,而且和 SQLAlchemy 的配合更成熟。如果你在安装时遇到编译报错,大概率是缺少系统级的依赖(gcc、python3-dev 等),更省事的方式是直接装纯 Python 的 pymysql,功能上完全够用:
bash复制docker compose exec superset pip install pymysql
装完驱动后,一定要重启 Superset 容器,否则新装的依赖可能不会被加载:
bash复制docker compose restart superset
6.2 编写连接串与连接设置
Superset 的数据库连接配置界面在 Settings -> Database Connections,点右上角的 + Database。在弹出的窗口里选择 MySQL,然后填连接串(SQLAlchemy URI):
code复制mysql+pymysql://root:rootpass@mysql:3306/sakila
拆开解释一下这一段:
mysql+pymysql:指定用 SQLAlchemy 方言和对应的 DB-API 驱动。如果你刚才是装mysqlclient,这里要改成mysql+mysqldb;root:rootpass:MySQL 的用户名和密码,和 compose 文件里的环境变量对应;mysql:3306:数据库地址。注意这里用的是 Compose 服务名mysql,而不是localhost。因为在容器内部,Superset 通过 Docker 内部网络访问 MySQL,localhost指向的是 Superset 容器自身,那里并没有 MySQL 进程;sakila:要连接的数据库名。
填好 URI 之后,点 Test Connection,Superset 会尝试连一下数据库。如果看到绿色对勾,说明连接成功。这时候在同一个弹窗里可以勾选 Expose database in SQL Lab 和 Allow file uploads to database 之类的选项,按需选择即可。保存后,你会在数据库列表里看到刚才添加的 sakila 连接。
6.3 通过 Dataset 和 SQL Lab 验证数据链路
连接建立之后,有两条路可以验证数据是否打通:
第一条路,在顶部的 Data -> Datasets 菜单里,点 + Dataset,选择刚才创建的 sakila 连接。这时候页面会列出 sakila 库里面所有的表,勾选几张核心表,比如 film、actor、rental、payment,点创建。之后你就能在 Chart 创建界面里直接选这些表作为数据源来设计图表,Superset 会自动扫描表结构和字段类型。
第二条路,直接进 SQL Lab,在数据库下拉框选择 sakila,然后在编辑区写一条查询验证一下数据:
sql复制SELECT
c.name AS category,
COUNT(f.film_id) AS film_count
FROM category c
JOIN film_category fc ON c.category_id = fc.category_id
JOIN film f ON f.film_id = fc.film_id
GROUP BY c.name
ORDER BY film_count DESC;
点 Run 执行,如果能看到各个电影分类的计数结果,说明整条链路——Superset、MySQL、Sakila——已经完全打通了。接下来你要做的就是用 SQL Lab 写探索性的查询,然后把结果保存成 Dataset,再基于 Dataset 创建图表、组装 Dashboard。
7. 实测复盘:我遇到的坑和解决思路
7.1 坑一:healthcheck 的凭据泄露在进程列表里
这是一个安全细节。我在 healthcheck 里直接用了 mysqladmin ping -uroot -prootpass,密码会暴露在容器进程的启动参数里。对于本地学习和测试环境,这不算大问题;但如果部署到生产环境,建议改用 .env 文件加载敏感配置,或者在 healthcheck 里通过环境变量引用密码,而不是硬编码。
简单的改进方式是这样:
yaml复制environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpass}
然后在项目根目录创建一个 .env 文件:
code复制MYSQL_ROOT_PASSWORD=your-strong-password
这样 compose 文件里不用写明文密码,也能通过环境变量统一管理。.env 文件要记得加入 .gitignore。
7.2 坑二:Superset 容器日志里出现 Fatal error in launcher
这个坑我碰到过两次,原因几乎一样:我用 docker compose exec superset pip install xxx 装驱动时,容器里的 pip 和环境里的 Python 版本对不上,导致安装后命令行工具找不到可执行文件。解决办法是先确认容器里的 Python 路径:
bash复制docker compose exec superset which python
docker compose exec superset python -m pip install mysqlclient
用 python -m pip 代替裸的 pip 执行安装,避免掉进多 Python 环境的坑。
7.3 坑三:Superset 连接 MySQL 时提示 Can't connect to MySQL server on 'mysql'
这个报错的原因通常是两个:一个是 MySQL 容器没起来,另一个是 Compose 网络没配对。由于我在 compose 文件里已经定义了 healthcheck 依赖,正常情况下 Superset 不会在 MySQL 未就绪时启动。但如果你改了网络配置,比如自定义了 network 别名,就得确认 Superset 和 MySQL 是不是在同一个 Compose 项目中。最简单的方式是别自定义网络,直接用 Compose 默认创建的项目网络。
如果你发现两个容器确实在同一个网络里,但连接还是失败,可以进入 Superset 容器手动测试一下到 MySQL 的连通性:
bash复制docker compose exec superset bash
# 容器内执行
curl -v telnet://mysql:3306
看到 Connected to mysql 就说明网络通了。
7.4 坑四:图表查询偶尔超时
Sakila 库的数据量不大,大部分查询都在毫秒级,但如果你在 SQL Lab 里跑一些跨表聚合、没有加索引的查询,Superset 默认的查询超时时间(大约 30 到 60 秒)还是有可能触发的。遇到这种情况,在 SQL Lab 设置里把超时调大即可,不建议在生产环境无脑调大,否则一个慢查询可能会拖垮数据库。
7.5 坑五:容器重启后登录状态丢失
这个问题一般是因为 Superset 的 secret key 在容器重建后发生了变化,导致会话签名失效。如果你用了命名 volume 持久化 superset_home,secret key 不变的话登录状态是可以保持的。解决办法是把你生成的 SUPERSET_SECRET_KEY 写死在 compose 文件的 .env 里,不要每次部署都重新生成。
8. 图表实战:拿 Sakila 做一张看板,验证整个链路
前面环境全通了,光连上数据库不画图有点浪费。我拿 Sakila 数据做了一张简单的看板,把"最赚钱的电影分类"和"每月租赁趋势"两个维度呈现出来,这能完整地走通从 Dataset 到 Chart 再到 Dashboard 的流程。
第一步:创建 Dataset。在 Data -> Datasets 里选择 sakila 连接,勾选 payment、rental、inventory、film、film_category、category 这几张表,创建。
第二步:在 SQL Lab 里写好图表所需的查询。比如我想看每个电影分类的租赁收入,SQL 如下:
sql复制SELECT
c.name AS category,
SUM(p.amount) AS total_revenue
FROM payment p
JOIN rental r ON p.rental_id = r.rental_id
JOIN inventory i ON r.inventory_id = i.inventory_id
JOIN film f ON i.film_id = f.film_id
JOIN film_category fc ON f.film_id = fc.film_id
JOIN category c ON fc.category_id = c.category_id
GROUP BY c.name
ORDER BY total_revenue DESC;
在 SQL Lab 里运行,确认数据没问题后,点 Explore 按钮,Superset 会把查询结果转成一个临时 Dataset,你可以在图表编辑器里选柱状图、饼图等可视化类型,直接生成图表。
第三步:在 charts 界面新建图表,数据源选择刚才创建的 Dataset,把 category 作为维度(Dimension),total_revenue 作为指标(Metric),选一个横向柱状图,保存。
第四步:Dashboards 里新建看板,把刚才的图表拖进去,再调整一下布局,一张简单的收入分析看板就完成了。整个链路从数据到展示,没有写一行前端代码。
这一步做完,你对 Superset 的完整使用流程就有了一个整体认知:环境怎么搭、数据怎么接、图表怎么出、仪表盘怎么组。后面再深入学比如高级计算字段、自定义 SQL 指标、定时邮件报表这些功能,都是在这套体系上继续叠加。
我个人在实际操作中的体会是,这套组合一旦跑通,以后再接任何别的数据库,比如 PostgreSQL、ClickHouse,都只是多写一个连接串的问题,整个部署框架可以复用。容器化部署的价值就在这里:环境一致性有了保障,换一台机器也只是把 compose 文件复制过去再跑一遍而已。
