资讯动态

用Scala+MySQL做交通拥堵预测:数据库课设全流程拆解

发布时间:2026/10/3 3:14:44 来源:尧图企业网站定制
简介基于Scala的交通拥堵预测源码源自大三“数据库课程设计”高分项目代码经验证可稳定运行面向计科、大数据、人工智能等方向的课设与大作业场景同时适合作为项目实战演练。压缩包共50个文件整体大小约73KB主体为18个Scala源文件另含Maven配置、模块描述、properties配置、Markdown笔记及少量HTML页面目录按tf_consumer、tf_producer、tf_prediction等模块划分便于快速定位业务链路目前已有106人学习浏览。项目涵盖数据消费、生产与交通拥堵预测的关键流程并体现出数据库课程设计所需的工程化组织结构初学者可借其快速跑通完整流程进阶使用者也能基于现有代码进行二次开发DIY更多功能。资源内还附有项目说明与课程设计相关的笔记文档可辅助理解从数据接入到预测输出的完整链路适合作为期末答辩、项目演示或继续研究的基础。1. 数据库课程设计最怕只会增删改查用 Scala 做拥堵预测正好补上短板数据库课程设计最怕交上去的作业只有一张表、四个按钮、两条 SQL老师看一眼就说“没有灵魂”。拥堵预测是少数能在数据库课程设计里同时展示建表功底、查询能力、分析思维的题目而基于 Scala 来做这条路的人不多但走通之后非常能打Scala 跑在 JVM 上开车就能连 MySQL 做数据读写再借助 Spark RDD 的创建和转换把特征计算写得比纯 Java 短一大截答辩时从数据模型讲到预测评估都有的聊。这篇笔记会把整个落地方案拆开从表结构设计、连接池配置、JDBC 读库、RDD 加工特征到滑动平均和多元回归两个预测模型、MAPE 评估最后落到四个真实踩坑记录。适合正在纠结数据库课程设计题目、又不想只会写 SQL 的同学也适合想把 Scala 和 MySQL 这条链路完整闭一遍再走的人。2. 数据库设计先行拥堵预测要存哪些表、字段怎么定2.1 为什么用“路况快照表”而不是“过车流水表”拿到“交通拥堵预测”这个题第一件事不是写 Scala而是想清楚数据怎么落库。很多同学上来就建一张流水表把每一辆过车的时间、车牌、位置全部记下来觉得这样数据最全。思路没错但课程设计的数据量根本撑不起这种设计等你要做预测的时候会发现所有特征都得靠二次聚合从流水里现算一条普通的“过去一小时平均速度”就要 group by 加聚合预测一次要跑小半天。更合适的做法是快照表每个检测点每隔 5 分钟记录一次当时的车流量、平均速度、时间占用率。这样一条记录代表“某个路段在某个时间窗口的状态”读出来直接就是特征不需要再做细腻度聚合。五分钟粒度是个平衡点五分钟一条一个检测点一天 288 条一个月不到 9000 条10 个检测点三个月也就 27 万行。这个量级用 MySQL 单表加索引完全可以覆盖也正好验证了“为什么这个课设不需要上大数据框架”。那什么时候才需要流水表只有当你想做更细的轨迹还原、路口转向分析时才需要。课程设计不必要建议直接把流水表砍掉把精力留给特征工程和模型解释。2.2 四张业务表加一张结果表建表 DDL 与索引选择我一般把库分成维度表和事实表两层维度表管“在哪里、什么时候”事实表管“状态是什么”。具体到交通拥堵预测这个库会建五张表表名类型职责dim_road维度表路段基本信息名称、长度、限速、方向dim_detector维度表检测点信息挂在哪个路段、位置描述fact_traffic_snapshot事实表每个检测点每个时间窗口的速度、流量、占有率dim_calendar维度表日期属性是否周末、是否节假日方便特征拼接pred_result结果表预测值、实际值、误差指标写完作业要能查事实表是核心所有模型训练数据都从它来结果表是输出用来评估模型效果也方便答辩时直接SELECT * FROM pred_result ORDER BY pred_time展示。下面是建表 SQL注意时间字段统一用DATETIME不要用TIMESTAMP原因在 2.3 里单独讲CREATE TABLE dim_road ( road_id INT PRIMARY KEY AUTO_INCREMENT, road_name VARCHAR(64) NOT NULL, length_m INT NOT NULL COMMENT 路段长度单位米, speed_limit INT NOT NULL COMMENT 限速 km/h, direction TINYINT NOT NULL COMMENT 1-上行 2-下行 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE dim_detector ( detector_id INT PRIMARY KEY AUTO_INCREMENT, road_id INT NOT NULL, location_desc VARCHAR(128) COMMENT 检测点位置描述, loc_lat DECIMAL(10,6), loc_lng DECIMAL(10,6), KEY idx_road (road_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE fact_traffic_snapshot ( id BIGINT PRIMARY KEY AUTO_INCREMENT, detector_id INT NOT NULL, snap_time DATETIME NOT NULL, traffic_volume INT NOT NULL COMMENT 5分钟车流量, avg_speed DECIMAL(5,1) NOT NULL COMMENT 平均速度 km/h, occupancy DECIMAL(4,2) NOT NULL COMMENT 时间占有率 0-100, KEY idx_detector_time (detector_id, snap_time), KEY idx_snap_time (snap_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE dim_calendar ( date_key DATE PRIMARY KEY, is_weekend TINYINT NOT NULL, is_holiday TINYINT NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE pred_result ( id BIGINT PRIMARY KEY AUTO_INCREMENT, detector_id INT NOT NULL, pred_time DATETIME NOT NULL, pred_level TINYINT NOT NULL COMMENT 0-畅通 1-缓行 2-拥堵, actual_level TINYINT COMMENT 回填真实等级, mape DECIMAL(8,6) COMMENT 该条预测的绝对百分比误差, model_name VARCHAR(32) NOT NULL, KEY idx_detector_predtime (detector_id, pred_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段 DDL 里有几个细节值得展开。第一事实表建的是复合索引(detector_id, snap_time)因为所有训练样本都是“按检测点取一段时间序列”这个条件最匹配复合索引的前缀规则。第二加了idx_snap_time是为了后面做全局时间范围过滤比如“只取最近三个月”的扫描条件。第三dim_calendar是典型的小维表数据量一年才 365 行但它能把“节假日是否影响拥堵”这个特征很好地拼进来这是很多课程设计忽略的点——只靠周几当特征遇到五一小长假预测会翻车。2.3 时间字段选 DATETIME 还是 TIMESTAMP直接影响踩不踩坑这里的选型在 Java/Scala 栈里经常被忽略但它是排在第一位的坑。TIMESTAMP在 MySQL 内部会按time_zone会话变量转成 UTC 存储读出来再转回会话时区DATETIME则存什么就是什么不带时区解释。课程设计只需要“北京时间”如果用了TIMESTAMPJDBC 连接串里的serverTimezone和 MySQL 全局时区一旦不一致查询按天分组就会莫名其妙少一小时第五章节会具体演示这个现象。所以建表阶段就敲定业务时间字段统一DATETIMEJava/Scala 侧使用LocalDateTime来读写生成String写库时用yyyy-MM-dd HH:mm:ss格式不传java.util.Date。这样从入库到查询时区只在一处控制容易排查。索引方面千万不要在snap_time上套函数再建索引比如DATE(snap_time)否则索引直接失效全表扫描。3. 从 Scala 连接池到 RDD 的创建读库与特征加工怎么做3.1 连接管理选 HikariCPDriverManager 裸连在批处理场景撑不住很多 Scala 课程设计代码喜欢用DriverManager.getConnection裸连图省事。单条查询没问题但拥堵预测要循环读几百个检测点的历史序列每个序列再算均值、方差、滞后特征一个训练周期可能要建立上千次连接。MySQL 默认max_connections也就一百多连接数一上去数据库端直接报Too many connections这还只是第一步频繁建连的握手开销会让整个训练慢三倍以上。我一般用 HikariCPSpring Boot 默认连接池就是它池化管理成熟。在build.sbt里加两个依赖libraryDependencies Seq( mysql % mysql-connector-java % 8.0.33, com.zaxxer % HikariCP % 4.0.3 )注意 Scala 本身不提供连接池它是 JDBC 之上的一层管理所以选型跟语言无关。Scala 2.12 与 JDK 8 的组合在这套依赖下最稳Scala 2.13 配合新版连接驱动也没问题。连接池初始化代码写成单例避免每个对象都 new 一个池import com.zaxxer.hikari.{HikariConfig, HikariDataSource} object DbPool { private val config new HikariConfig() config.setJdbcUrl(jdbc:mysql://localhost:3306/traffic_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai) config.setUsername(root) config.setPassword(your_password) config.setMaximumPoolSize(5) config.setMinimumIdle(2) config.setConnectionTimeout(3000) config.setPoolName(TrafficPool) private val ds new HikariDataSource(config) def getConnection ds.getConnection }这里几个参数值得说。maximumPoolSize设 5 而不是默认 10课程设计是单机训练5 个连接完全够用池子越大不代表越快反而会增加 MySQL 端线程开销。connectionTimeout3000的意思是拿不到连接最多等 3 秒就抛异常避免训练脚本卡死靠人工发现。serverTimezoneAsia/Shanghai是配合上一章 DATETIME 选型的JDBC 驱动连接时如果没指定它会拿 JVM 默认时区时区漂移的隐患就出在这。这里补一句数据库连接池常见误用线程在 finally 里调用connection.close()时如果有未提交事务或未关闭的 ResultSet连接归还到池里会带着脏状态。所以后面写数据访问代码时我习惯把连接的 autoCommit 显式设成 false全部数据处理完再提交。3.2 读库到 RDD 的创建把 JDBC 结果集变成 RDD既然标题点到了 RDD这里说清楚RDD 是 Apache Spark 的抽象Scala 语言本身没有 RDD但是 Scala 是操作 RDD 最自然的一门语言。课程设计如果只做离线预测可以只把 Spark 当计算引擎引进来用它的 JDBC 读取能力省掉自己写多线程遍历结果集的功夫。依赖还要追加两个org.apache.spark %% spark-core % 3.3.2, org.apache.spark %% spark-sql % 3.3.2用 SparkSession 的read.jdbc一次性把训练数据读成 DataFrame再转成 RDD 做特征映射这是 RDD 的创建里最常见的入口之一import org.apache.spark.sql.SparkSession val spark SparkSession.builder() .appName(TrafficPrediction) .master(local[2]) .getOrCreate() val reader spark.read .option(driver, com.mysql.cj.jdbc.Driver) .option(user, root) .option(password, your_password) .jdbc(jdbc:mysql://localhost:3306/traffic_db?serverTimezoneAsia/Shanghai, fact_traffic_snapshot, id, 1000L, 1, 100000L) val trafficRdd reader .select(detector_id, snap_time, traffic_volume, avg_speed, occupancy) .rdd这段代码里jdbc方法带的那组参数是分区策略按id列把数据分成 1000 个分区每个分区一条WHERE id BETWEEN ? AND ?的查询并行读库。课程设计数据量不大困惑点在于为什么要分区——这是 Spark 读取 JDBC 的机制分区是为了并行不是必须。数据量只有几十万行时直接.jdbc(url, table, props)读单分区反而更快因为少了任务调度开销。我建议你先用单分区跑通再改分区优化。trafficRdd拿到的是RDD[Row]Row 是 Spark SQL 的行对象后面特征加工要把它 map 成自己的 case class。3.3 特征工程把数据库记录变成模型能吃的结构化特征模型预测拥堵不是把avg_speed直接扔进去就完事。要预测下一个时刻的拥堵等级特征至少要有三组时间属性、近期状态、周期性。时间属性包含小时、是否周末、是否节假日近期状态是过去一个小时的滑动平均速度和流量周期性是“上周同一天同一时刻”的历史速度。数据还是从 fact 表里来用 SQL 提前算好落到一张宽表里比在 Scala 里 join 省事得多。我一般这样建宽表特征查询SELECT t.detector_id, DATE_FORMAT(t.snap_time, %H) AS hour_of_day, c.is_weekend, c.is_holiday, t.avg_speed, t.traffic_volume, (SELECT AVG(avg_speed) FROM fact_traffic_snapshot s WHERE s.detector_id t.detector_id AND s.snap_time BETWEEN DATE_SUB(t.snap_time, INTERVAL 1 HOUR) AND t.snap_time ) AS avg_speed_1h, (SELECT AVG(avg_speed) FROM fact_traffic_snapshot s WHERE s.detector_id t.detector_id AND s.snap_time BETWEEN DATE_SUB(t.snap_time, INTERVAL 7 DAY) AND DATE_SUB(t.snap_time, INTERVAL 7 DAY) ) AS avg_speed_last_week_same_time FROM fact_traffic_snapshot t LEFT JOIN dim_calendar c ON DATE(t.snap_time) c.date_key这段 SQL 的坑在于子查询里INTERVAL 7 DAY这种写法它算的是“七天前同一时刻的前后几分钟”如果事实表有缺失结果会是 NULL模型训练时遇到 NULL 需要丢弃样本或者用该检测点的全局平均速度填充。读回 Scala 侧后把 Row 映射成 case class这一步就是把 RDD 的创建和价值落地的地方case class TrafficSample( detectorId: Int, hourOfDay: Int, isWeekend: Int, isHoliday: Int, avgSpeed: Double, trafficVolume: Int, avgSpeed1h: Double, avgSpeedLastWeek: Double, congestionLevel: Int ) val samples trafficRdd .filter(r !r.isNullAt(6) !r.isNullAt(7)) .map { row val speed row.getDouble(2) val level if (speed 45) 0 else if (speed 30) 1 else 2 TrafficSample( detectorId row.getInt(0), hourOfDay row.getString(1).toInt, isWeekend row.getInt(2), isHoliday row.getInt(3), avgSpeed speed, trafficVolume row.getInt(4), avgSpeed1h row.getDouble(5), avgSpeedLastWeek row.getDouble(6), congestionLevel level ) } .filter(_.avgSpeed1h 0)这个map里有几个细节容易错。第一Row的列下标是从 0 开始和 select 出的列顺序一致如果 select 顺序调整了下标必须跟着变否则取到错列。第二拥堵等级的阈值 45 和 30 是人为划定的标准不太能直接拿到就得自己定义。第三最后一行filter(_.avgSpeed1h 0)是把第一小时内有缺失的样本丢掉这类样本在计算滑动平均时本来就是部分窗口留着只会制造噪声。到这里特征已经变成了RDD[TrafficSample]接下来可以把它 collect 成本地集合喂给回归模型也可以继续用 RDD 做聚合评估。4. 预测模型与评估滑动平均打底线性回归拔高4.1 先跑一个滑动平均基线预测结果得有个最低参照很多同学第一次写预测就上回归、上神经网络结果答辩被问“怎么证明你的模型有效”时拿不出参照系。一个项目里必须先有一个简单到不能再简单的基线模型后面所有复杂模型的效果都要跟它对比。滑动平均就是最好的基线预测下一个时刻的速度等于过去 N 个同时段速度的平均值。在 Scala 里用样本的avgSpeedLastWeek字段就能直接出一个基线预测因为它在 SQL 阶段算的正好就是“上周同时段平均速度”。评估时以它为预测值计算和实际值的误差val baseMape samples.map { s val pred s.avgSpeedLastWeek val actual s.avgSpeed Math.abs((actual - pred) / actual) }.mean()这段代码跑出来的 MAPE 通常在 20% 上下取决于数据本身的周期性有多强。它的意义在于告诉你一个数字假如简单基线就能做到误差 18%那后面回归模型如果只做到 17%说明模型没学到额外信息需要重新看特征如果回归能做到 10% 以内说明时间、节假日、近一小时状态这些特征确实在起作用。这个对比逻辑要写进课程设计的报告里本身就是加分项。4.2 多元线性回归用 Breeze 写最小二乘不引重型依赖预测算法我一般选多元线性回归原因很实际数据量不大特征维度少线性回归可解释性强答辩被追问时讲得清楚系数含义。实现上不引入 Spark MLlib因为那会把整个 Spark 机器学习依赖链全部拖进来课程设计没必要。用 Breeze 这个 Scala 数值计算库就够了它在breeze.linalg包里提供了矩阵运算正规方程十行代码就能写出最小二乘解。依赖加一行org.scalanlp %% breeze % 2.1.0, org.scalanlp %% breeze-viz % 2.1.0正规方程的解是beta (X^T X)^(-1) X^T y样本数量级在万级别时直接求矩阵逆没问题百万级别再用 QR 分解。下面是完整的训练和预测函数import breeze.linalg._ def trainLinearRegression(samples: Array[TrafficSample]): DenseVector[Double] { val xMat samples.map { s DenseVector[Double](1.0, s.hourOfDay, s.isWeekend, s.isHoliday, s.avgSpeed1h, s.avgSpeedLastWeek) } val X: DenseMatrix[Double] DenseMatrix(xMat: _*) val y: DenseVector[Double] DenseVector(samples.map(_.avgSpeed): _*) val XtX X.t * X val XtXInv inv(XtX) val XtY X.t * y XtXInv * XtY } def predictOne(beta: DenseVector[Double], s: TrafficSample): Double { beta dot DenseVector[Double](1.0, s.hourOfDay, s.isWeekend, s.isHoliday, s.avgSpeed1h, s.avgSpeedLastWeek) }代码里特征矩阵的每一行是[1, hourOfDay, isWeekend, isHoliday, avgSpeed1h, avgSpeedLastWeek]开头的1.0对应偏置项。hourOfDay的取值范围是 0 到 23avgSpeed是 0 到 70 左右这两个量纲差很多正规方程对特征缩放敏感所以跑训练前最好先标准化特征。一个简单做法是对连续特征整体做 Z-scoredef standardize(values: Array[Double]): Array[Double] { val mean values.sum / values.length val std math.sqrt(values.map(v (v - mean) * (v - mean)).sum / values.length) values.map(v (v - mean) / std) }关于特征标准化还是要在报告里给一句话如果不标准化hourOfDay和avgSpeed1h的系数估计会不稳定因为X^T X的条件数过大inv结果受舍入误差影响显著。Breeze 的inv走的是 LAPACK小规模矩阵没有大问题但演示时特征标准化的代码能体现你理解数值问题属于答辩的隐藏加分项。4.3 模型评估与回写结果表MAPE、RMSE 和批量写入模型不能只看训练集误差。我会按时间把数据切一刀前 80% 的时间段做训练后 20% 做验证这样保证模型没见过未来数据。在 RDD 上切时间可以用一个简单 filterval splitTime 2024-06-01 00:00:00 val (trainRdd, testRdd) samples.rdd.randomSplit(Array(0.8, 0.2), seed 42L)注意randomSplit是随机切分对时间序列不合理它会同一时刻的数据一部分进训练一部分进验证造成信息泄漏。时间序列的正确切分是用固定时间点把前后切开val trainRdd samples.filter(_.snapTime splitTime) val testRdd samples.filter(_.snapTime splitTime)这里要求TrafficSample里带上snapTime字段上一小节的 case class 里我刻意没加实际使用时需要补上切分逻辑比随机切分靠谱也更容易在答辩里讲清楚。评估指标算两个一个是 MAPE一个是 RMSE各有侧重。MAPE 是相对误差的百分数能看到模型总体偏差比例RMSE 对绝对误差的大值更敏感能看出模型是不是在某些极端拥堵时刻崩掉。计算代码val trainBeta trainLinearRegression(trainRdd.collect()) val testMetrics testRdd.map { s val pred predictOne(trainBeta, s) val actual s.avgSpeed (math.abs((actual - pred) / actual), (pred - actual) * (pred - actual)) }.collect() val mape testMetrics.map(_._1).sum / testMetrics.length val rmse math.sqrt(testMetrics.map(_._2).sum / testMetrics.length)跑完评估最后一步把预测结果和误差写回pred_result表这一步既完成数据库课程设计的“写”操作又给答辩留了查询素材。写入用 PreparedStatement 加批量提交避免一条一条提交事务val conn DbPool.getConnection conn.setAutoCommit(false) val ps conn.prepareStatement( INSERT INTO pred_result(detector_id, pred_time, pred_level, actual_level, mape, model_name) VALUES (?,?,?,?,?,?) ) testRdd.collect().foreach { s val pred predictOne(trainBeta, s) val level if (pred 45) 0 else if (pred 30) 1 else 2 val actualLevel s.congestionLevel val mape math.abs((s.avgSpeed - pred) / s.avgSpeed) ps.setInt(1, s.detectorId) ps.setString(2, s.snapTime) ps.setInt(3, level) ps.setInt(4, actualLevel) ps.setDouble(5, mape) ps.setString(6, linear_regression) ps.addBatch() } ps.executeBatch() conn.commit() conn.close()这里强调两点。第一conn.setAutoCommit(false)后如果中间抛异常必须在 catch 里rollback()否则连接归还到池里时会带着未提交事务下一条数据可能读到脏数据。第二conn.close()在连接池场景里是“归还连接”而不是“关闭连接”executeBatch()之后关掉 Statement 再归还连接是避免连接池耗尽的基本习惯。5. 避坑记录Scala 加 MySQL 的四个真实翻车现场5.1 现象按天分组统计结果天天少一小时有次跑评估发现预测结果和 actual 对不上按天统计预测数量每天只有 23 个小时的数据。去 MySQL 里查同一时间段数据又是完整的。原因JDBC 连接串里没设置serverTimezoneJava 侧用LocalDateTime传参但连接会话时区与数据库服务器时区不一致DATETIME字段本身没问题问题出在 SQL 里WHERE snap_time ? AND snap_time ?的边界值被 JDBC 做了时区转换凌晨那一个小时被挪到了前一天。解决JDBC URL 后面强制拼上serverTimezoneAsia/Shanghai并且 Scala 侧所有时间参数用LocalTime对齐到北京时间字符串再传入不要用java.util.Date传参。这个坑最隐蔽的地方在于它不是必现的只有跨到凌晨零点边界的数据才暴露。5.2 现象任务跑完MySQL 的 Threads_connected 还挂着一百多用连接池跑批量预测日志里没有任何报错但SHOW STATUS LIKE Threads_connected显示连接数不降最后把 MySQL 默认连接数打满后续请求全部失败。原因代码里把DbPool.getConnection拿到的连接当普通 JDBC 连接用执行完 SQL 后没有调用close()。在 HikariCP 里close()是归还连接不调用它连接就一直被占用。我排查时发现是循环里parallel并行处理后每个线程拿到的连接都没有走 finally。解决所有连接操作放进 try-finallyfinally { conn.close() }或者直接 Scala 的Loan Pattern封装一层def withConnection[T](fn: java.sql.Connection T): T { val conn DbPool.getConnection try fn(conn) finally conn.close() }这个封装比裸写 try-finally 舒服也杜绝了“忘了归还连接”的重复踩坑。5.3 现象Scala 里 new 一个 java.util.ArrayListforeach 直接报 ClassCastException在 Scala 2.12 项目里写val list new java.util.ArrayList[Int]()然后list.foreach(println)编译能过运行期抛ClassCastException。原因Scala 2.12 之前Java 集合和 Scala 集合之间没有隐式转换java.util.ArrayList的foreach方法签名和 Scala 函数类型不兼容Scala 编译器尝试按 Scala 集合的foreach来解释运行时类型检查失败。这是 Scala 版本差异的经典坑网上经常有人拿它来对比 Scala 的“隐式转换有多坑”。解决在 Scala 2.12 里创建 Scala 自己的集合类ListBuffer或ArrayBuffer如果必须从 Java 集合转用scala.collection.JavaConverters._下的asScala方法import scala.collection.JavaConverters._ val scalaList new java.util.ArrayList[Int]().asScala注意到了 Scala 2.13这个包挪到了scala.jdk.CollectionConverters项目里两个版本混用会直接编译失败。课程设计如果用 Scala 2.13直接把asScala的 import 换成scala.jdk.CollectionConverters._。5.4 现象MySQL 8 连不上报 Public Key Retrieval is not allowed代码在本地跑得好好的放到服务器上连数据库就报错错误信息里带Public Key Retrieval is not allowed或者直接说caching_sha2_password认证失败。原因MySQL 8 默认认证插件是caching_sha2_password而旧版驱动或者不加密连接时客户端需要向服务器请求 RSA 公钥来传递密码。连接串里没允许公钥检索服务器就拒绝认证。解决两种改法选一个。第一种把连接串加上allowPublicKeyRetrievaltrueuseSSLfalse让客户端自动拿公钥第二种更推荐把 MySQL 用户改回mysql_native_password认证插件但这需要服务器权限ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY your_password;我不太建议为了省事把useSSLfalse作为默认配置带进正式项目本地课设图省事尚且可以但报告里最好提一句“生产环境应使用 SSL 连接”。6. 从能跑变成高分答辩演示顺序与两个扩展方向代码全部跑通只是课程设计的开始分数高低取决于答辩那几分钟老师看到了什么。我一般按这个顺序演示先打开pred_result表做一条SELECT detector_id, pred_time, pred_level, actual_level, mape FROM pred_result ORDER BY mape DESC LIMIT 10让老师看到最差预测长什么样主动暴露缺点比被问出来体面再用 ECharts 把预测速度和实际速度画成两条折线图时间轴选连续 48 小时能直观看到晚上预测准、早晚高峰偏差大最后亮出 MAPE 对比表说清楚基线模型误差多少、线性回归误差多少、哪些特征贡献最大。答辩追问一般集中在两个地方。一个是“为什么用 Scala 而不用 Python”可以从三方面应对Scala 与 JDBC 类型安全连接池集成在 JVM 生态内特征处理可以平滑迁移到大数据的 RDD 计算模型数据量上去之后不用换代码课程设计数据管路的重点是数据库操作规范性Scala 表达力强但约束也强。另一个是“你这个模型是不是太简单了”不要慌线性回归的可解释性就是卖点把beta向量打印出来指出avgSpeed1h的系数最大说明近一小时拥堵是预测未来拥堵最主要的信号这个结论本身就很有交通工程味道。如果还有余力两个扩展方向值得在报告里留一个“展望”段。一是把批量预测改成准实时用定时调度每 5 分钟读一次最新快照走 RDD 增量更新特征预测未来 15 分钟拥堵等级此时对 MySQL 的依赖从“离线训练”变成“在线特征查询”连接池和索引设计要跟着调整。二是多源数据库同步场景如果有两个校区或两个城市的交通库可以写一个简单的同步任务把分库的数据清洗后汇入汇总库再训练这个方向完全围绕数据库课程设计的知识点展开不会跑题。做课程设计这些年我最大的教训是不要等代码全写完再想演示和答辩而是把“这页 PPT 老师会问什么”当成编码的一部分。认真做索引、认真做连接池释放、认真做评估对比这些细节才是高分和高分的分水岭。希望这篇从表结构走到踩坑记录的笔记对你有用也祝你答辩时稳稳站住脚。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑