大概需要实现的效果:用单片机做一个闹钟。支持设置闹铃的时间和种类,到点时会在该种类里随机一首歌。可能需要联网,上传闹铃播放的时间存储在数据库里,也可能会联网找歌。
最近会有别的外出活动,因此先解决当中偏软件的部分。数据的量是小的,因此我们采用 Python + SQLite。
1. SQL 简介
SQL stands for Structured Query Language,基础目的是实现这样一种数据结构:
- 定义结构:建立表格,每一列是什么类型,以及相应的约束,插入的数据不能违反约束,常见的约束有:
- NOT NULL
- UNIQUE
- PRIMARY KEY:一张表只能有一个主键,用来唯一表示这一行数据,相当于 NOT NULL + UNIQUE
- CHECK:只允许某些特定的键值
- FOREIGN KEY:儿子表的某一列必须匹配父亲表的某一列。
- 操作,至少支持以下操作:
- INSERT
- UPDATE
- DELETE
- 查询(SELECT),至少支持以下操作:
- 全表扫描
- 对于某个信息的单点查询,区间查询等
- 部分支持模糊匹配的查询,比如特定前缀
- 事务与持久化能力
- 原子性 Atomicity:要么全做完,要么直接罢工
- 一致性 Consistency:做之前是对的,做之后也是对的
- 隔离性 Isolation:多个程序同时在写数据库时,互不干扰
- 持久性 Durability:就算突然断电,数据也不会丢
其规则非常严格,不会接受脏数据,如果这个数据有问题会报错。
从查询可以看出其大概是通过平衡树实现的,因为其并不支持复杂的模糊匹配——比如搜索引擎,大概是通过把每个网页拆成关键词,然后某些关键词出现在了哪些网页里。确实有工具让 SQL 支持类似于这样的功能,但是以后再说。
我们采用 SQLite3 做一个简单的实验。假设现在有三个闹钟,分别叫做卧室、客厅和厨房,如果只用一张表然后某一列代表是哪个闹钟响的,如果某天客厅变书房了,那么完蛋了。
因此比较好的实现方式是父亲表 devices 存放设备清单,儿子表 alarm_logs 存放闹钟记录但是不存设备名字只存编号。
1 | -- 1. 创建父亲表(设备表) |
这里我们先插入 devices:
1 | INSERT INTO devices (device_name) VALUES ('bedroom'); |
这样插入日志表的时候,device_ref 就有限制,不能乱写:
1 | INSERT INTO alarm_logs (alarm_time, category, song_name, device_ref) VALUES ('2026-07-06 07:00:00', 'pop', '0001.mp3', 1); |
1.1 实现数据结构
主要写一下思路。
查找的效率是重要的,但是我们不可能对所有列都建一棵平衡树(通常只默认对 PRIMARY KEY 建),否则磁盘空间爆炸了,插入的时间复杂度也爆炸了。
而且不能采用二叉树,因为数据是存在磁盘上的。对于一次寻找儿子的操作,都意味着随机访问,这个是很慢(约 10ms,而内存是纳秒级的)
数据很多不可能存在内存里,但是该