🍓 第五章 使用树莓派架设服务器
📄 一句话概括

到上一节为止,服务器已经「会算」了,但它一关机就什么都忘了。本节装上数据库,让它能把东西「记下来」——用户、留言、订单,都存在这里,重启也不丢。 这是 LAMP 里的第三块,也是本节最容易踩坑的一块:新版数据库的登录方式跟老教程完全不一样。

📝 本节和前面几节的关系

5-1 装 Apache(接待)、5-2 装 PHP(现算),本节装数据库(记账)——三块凑齐,就是一个完整的动态网站后台。 这门课的第 15 门《完全图解数据库》讲过库、表、记录、SQL 四类语句——本节是那门课第一次「真的装上一个数据库」,把纸上的概念变成机器里的东西。 本节是全课第一个「要会算」的 ⭐⭐⭐:SQL 语句得动手敲,权限得亲手授。

一、为什么服务器需要数据库

你可能会想:存数据用文件不就行了? 4-2 学的 > 重定向、cat 都在啊。

文件能存,但有四件事做不到:

需求 文件 数据库
多人同时写(两个用户同时下单) 会互相覆盖、写坏 有锁与事务,保证不打架
快速查找(10 万条里找一个用户) 只能从头翻到尾 有索引,几步就定位
结构约束(年龄不许填「哈哈」) 管不了 可以定义类型与规则(4-2 那门课的「避免非法数据」)
断电不丢(正写着断电) 可能写一半,文件坏了 有日志,能恢复到一致状态
💡 一句话记住

文件是「笔记本」,数据库是「档案室」。 记一两句用笔记本挺好;要管几万份材料、还要多人同时借还、还得能查、能追责——就得有档案室。

二、先搞清一件事:你会装到的是 MariaDB

这是本节第一个反直觉的地方。

sudo apt install mysql-server     # 在 Debian / 树莓派上,这句装的是 MariaDB

MariaDB 是什么? 简单说:MySQL 被 Oracle 收购后,原作者带团队另开的分支,跟 MySQL 几乎完全兼容——同样的 SQL、同样的协议、同样的客户端。Debian 系(包括 Raspberry Pi OS)默认仓库里就是它。

MySQL MariaDB
谁维护 Oracle 原 MySQL 团队 + 社区
在 Debian 仓库里 不是默认(要另加官方源) 是默认
命令基本一样吗 —— 基本一样,日常使用感觉不到差别
📝 本课统一用哪个?

统一装 mariadb-server(这就是 Debian 上「装数据库」的正路)。 后面正文里的命令,绝大多数和你查到的 MySQL 教程是通用的——只有「登录方式」和「命令名字」两处不同,而这两处正是本节的防坑重点。

三、装:三行命令

sudo apt update
sudo apt install mariadb-server -y
systemctl status mariadb --no-pager

注意服务名是 mariadb,不是 mysql(老教程里的 systemctl restart mysql 在这会报 Unit mysql.service not found)。

期望看到 Active: active (running)。

再确认一下版本:

mariadb --version

期望输出类似 mariadb from 11.8.x-MariaDB(具体版本以你机器为准)。

四、最大的坑:root 不用密码,也「不能」用密码

这一条是本节最值钱的内容。 老教程会让你这样登录:

mysql -u root -p        # 然后输入你设的密码

在当前版本上,这会失败——而且失败得很奇怪:你从来没设过密码,因为安装时根本没让你设。

真相是:MariaDB 从 10.4 起,root 默认改用 unix_socket 认证。

💡 什么是 `unix_socket` 认证

它不看密码,看你是谁——准确地说,看你的 Linux 系统用户身份。 如果你是以系统 root(sudo)身份连过来的,数据库就认为「你是 root,放行」。 这是更安全的做法:密码可能被猜、被撞库,但「你已经通过系统认证成为 root」这件事没法伪造。

所以正确的登录方式是:

sudo mariadb

期望结果:直接进入 MariaDB [(none)]> 提示符,不问你密码。

-- 在数据库里查一下 root 用的是哪种认证
SELECT user, host, plugin FROM mysql.user WHERE user = 'root';

期望看到 plugin 那一列是 unix_socket。看到它就说明「我不是遇到 bug,而是碰到了新版本的正常设计」。

⚠️ 三处由此产生的「老教程失效」
  • mysql -u root -p 登不进 —— 因为没密码可输,改用 sudo mariadb;
  • 教程里的「设置 root 密码」步骤对你没用 —— 除非你确实要用密码方式(比如给某个程序用),那得显式改认证方式,见下一节;
  • mysql_secure_installation 这个名字变了 —— 现在叫 mariadb-secure-installation(旧名大多还留着兼容,但新资料里一律是新名字)。

五、跑一遍安全脚本(会问你几个问题)

sudo mariadb-secure-installation

它会依次问几件事,几个关键问句怎么答(这几个答错会给自己挖坑):

它会问 建议答 为什么
是否切换到 unix_socket 认证 Y(一般默认就是它) 保持「用 sudo 才能管数据库」这种最安全的状态
设置 root 密码 若上一问答 Y,则跳过 socket 认证本身不用密码
删除匿名用户 Y 匿名账号是纯粹的后门
禁止 root 远程登录 Y root 只该在机器本机用
删除 test 数据库 Y 无用的默认库
重新加载权限表 Y 让刚才的改动生效

跑完看到 Thanks for using MariaDB! 就算成功。

六、给它建一个「专用账号」——不要用 root 跑网站

这是本节最该带走的习惯(4-3 那门课讲的「最小权限原则」在这里落地):

🚨 铁律

网站程序永远不要用 root 连数据库。 一旦网站被攻破,攻击者用 root 的权限就能删掉整个数据库服务器上的一切。给它一个只能在「某一个库」里干活的账号——这样最多损失一个库。

四步走(sudo mariadb 进去后执行):

-- ① 建一个库
CREATE DATABASE myapp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- ② 建一个专用用户(注意:密码要换成你自己的,别照抄)
CREATE USER 'webuser'@'localhost' IDENTIFIED BY 'ChangeThisPass123';

-- ③ 只把 myapp 这个库的权限给它
GRANT ALL PRIVILEGES ON myapp.* TO 'webuser'@'localhost';

-- ④ 让权限生效
FLUSH PRIVILEGES;

几个要点:

验证一下这个账号能进去(不用 sudo,走密码):

mariadb -u webuser -p myapp

输入密码后能进 MariaDB [myapp]>,说明专用账号建对了。

七、建一张表,塞点数据(回收第 15 门的 SQL)

USE myapp;

CREATE TABLE visitors (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(50) NOT NULL,
  visited_at DATETIME
);

INSERT INTO visitors (name, visited_at) VALUES ('波仔', NOW());
INSERT INTO visitors (name, visited_at) VALUES ('小明', NOW());

SELECT * FROM visitors;

期望输出:两行记录,id 从 1 开始自增。

几条常用的查看命令:

SHOW DATABASES;      -- 有哪些库
USE myapp;           -- 切到某个库
SHOW TABLES;         -- 这个库有哪些表
DESCRIBE visitors;   -- 看表结构(列名、类型、是否可空)
SELECT COUNT(*) FROM visitors;   -- 数一数有几条

退出数据库:输入 exit 或按 Ctrl + D。

八、让 PHP 把数据显示出来(三块拼在一起)

这一步做完,你就真的把 LAMP 串起来了。

新建 /var/www/html/db.php:

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>数据库测试</title></head>
<body>
<h1>从数据库里读出来的记录</h1>
<?php
$conn = new mysqli("localhost", "webuser", "ChangeThisPass123", "myapp");

if ($conn->connect_error) {
    die("连接失败:" . $conn->connect_error);
}
$conn->set_charset("utf8mb4");   // 不给它设,中文还是可能乱码

$result = $conn->query("SELECT name, visited_at FROM visitors ORDER BY id DESC LIMIT 5");
while ($row = $result->fetch_assoc()) {
    echo "<p>" . htmlspecialchars($row["name"]) . " —— " . $row["visited_at"] . "</p>";
}
$conn->close();
?>
</body>
</html>

浏览器打开 http://树莓派的IP/db.php,期望看到刚才插进去的那两条记录。

📝 两个小东西值得留意
  • set_charset("utf8mb4") —— 建库时指定了编码,连接时还要再声明一次,两头都对上中文才稳;
  • htmlspecialchars() —— 把用户输入里的特殊字符转义掉,防的是「用户输入被当成代码执行」(网页安全的第一道门)。养成习惯,凡是往页面上输出的用户数据,都套一层。

九、配置与文件都在哪儿

几张「地图」要认,出问题时知道去哪儿看:

位置 是什么
/etc/mysql/mariadb.conf.d/50-server.cnf 主配置(端口、监听地址都在这)
/var/lib/mysql/ 数据目录——所有库和表都躺在这(备份时就备份它)
/var/log/mysql/error.log 数据库自己的错误日志
/etc/mysql/debian.cnf 系统维护脚本用的凭据(别乱改)
~/.my.cnf 你自己的登录凭据(可选,省得每次输密码)

其中一个设置要特别说:

grep -r "bind-address" /etc/mysql/mariadb.conf.d/
# 默认:bind-address = 127.0.0.1

127.0.0.1 的含义是「只听本机」——别的机器连不上它。这是默认的安全设置,别去改。

⚠️ 别照着「让 MySQL 支持远程访问」的教程改

网上大量教程教你把它改成 0.0.0.0、再建个 '%' 的用户,好让别的机器连。 本课场景完全不需要(PHP 和数据库在同一台树莓派上),改了只是凭空开了一扇门。 确实需要远程管数据库时,正确做法是走 SSH 隧道,而不是把数据库端口暴露出去。

十、动手 Demo:一条数据从「表单」走进「数据库」

目标:把本节所有零件串成一条完整链路——你在浏览器填、PHP 收、数据库存、再读回来显示。

sudo nano /var/www/html/add.php
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>登记</title></head>
<body>
<h1>登记一位访客</h1>

<?php
$conn = new mysqli("localhost", "webuser", "ChangeThisPass123", "myapp");
if ($conn->connect_error) { die("连接失败:" . $conn->connect_error); }
$conn->set_charset("utf8mb4");

if ($_SERVER["REQUEST_METHOD"] === "POST" && !empty($_POST["name"])) {
    $stmt = $conn->prepare("INSERT INTO visitors (name, visited_at) VALUES (?, NOW())");
    $stmt->bind_param("s", $_POST["name"]);
    $stmt->execute();
    echo "<p>已登记:" . htmlspecialchars($_POST["name"]) . "</p>";
}
?>

<form method="post">
  姓名:<input type="text" name="name">
  <button type="submit">提交</button>
</form>

<hr>
<?php
$result = $conn->query("SELECT name, visited_at FROM visitors ORDER BY id DESC LIMIT 10");
while ($row = $result->fetch_assoc()) {
    echo "<p>" . htmlspecialchars($row["name"]) . " —— " . $row["visited_at"] . "</p>";
}
$conn->close();
?>
</body>
</html>

验收四步:

  1. 浏览器打开 add.php,看到输入框和已有的记录;
  2. 输入一个名字,点提交,页面上出现「已登记:xxx」;
  3. 刷新页面,那条记录出现在下面列表里(说明真的写进数据库了);
  4. 回到终端验证: bash sudo mariadb -e "SELECT * FROM myapp.visitors;" 能看到你刚提交的那条就算彻底成功。

读结果:

📝 关于 `prepare` / `bind_param`

代码里没直接把用户输入拼进 SQL,而是用了参数化查询。 这是为了防SQL 注入(用户输入被当成 SQL 代码执行)——这是所有数据库教程里最重要的一条安全习惯。现在只要记住「有用户输入就用 prepare」这个动作,原理以后细讲。

十一、30 秒实验:看见「数据真的存在磁盘上」

# 看一眼数据目录(需要权限)
sudo ls -lh /var/lib/mysql/myapp/

期望看到 visitors.ibd 之类的文件——这就是你刚才那两条记录在磁盘上的物理形态。

# 看一眼数据库服务的状态和启动时间
systemctl status mariadb --no-pager | head -5

再做个「重启也不丢」的验证:

sudo systemctl restart mariadb
sudo mariadb -e "SELECT COUNT(*) FROM myapp.visitors;"

记录数不变 —— 这就回答了本节开头那个问题:为什么需要数据库。文件可能写坏,数据库能保证「写下去了就是写下去了」。

十二、可选:装个图形界面管数据库(phpMyAdmin)

不想老敲 SQL 的话,可以装网页版的数据库管理工具:

sudo apt install phpmyadmin -y

安装过程中会问你「用哪个 Web 服务器」→ 选 apache2;再问「要不要用 dbconfig 配置数据库」→ 选 Yes,然后设一个管理密码。

装完访问:http://树莓派的IP/phpmyadmin,用 webuser 或 root(socket 方式可能需另设密码)登录。

⚠️ 用完的场景要关掉它

phpMyAdmin 是直连数据库的管理面板——挂在网上等于把数据库钥匙插在门上。 本地学习用没问题,但只要这台机器有半点外网暴露(哪怕路由器转了个端口),先把 phpmyadmin 目录删掉或用 Apache 的访问控制锁住。这是 5-1 那条红线(绝不暴露公网)的具体应用。

十三、常见误区

  1. 「apt install mysql-server 装的是 MySQL」——在 Debian / 树莓派上,它给你的是 MariaDB。这是官方仓库的选择,不是 bug。
  2. 「mysql -u root -p 登不进去,说明装坏了」——没坏,是新版本的正常设计。root 默认走 unix_socket,用 sudo mariadb 登。(见第四节)
  3. 「网站用 root 连数据库比较省事」——这是最危险的一条。永远建专用账号,只授必要权限(第六节)。
  4. 「utf8 就是 UTF-8」——不是。MySQL/MariaDB 里的 utf8 是不完整的旧编码,存 emoji 会出错。一律用 utf8mb4。
  5. 「改 bind-address 成 0.0.0.0 才能用」——本课不需要。PHP 和数据库同机,走 localhost 就够。改了只是无谓地开门。
  6. 「SQL 里把用户输入拼进语句没关系」——这是 SQL 注入。有用户输入就用 prepare + bind_param。
  7. 「重启一下数据库服务,数据就没了吧」——不会。数据在 /var/lib/mysql/ 的磁盘文件里,服务重启不影响它。(这也正是数据库存在的意义)
⚠️ 老教程里已失效的写法
  • mysql_secure_installation —— 现在叫 mariadb-secure-installation(旧名多为兼容保留,新资料用新名);
  • systemctl restart mysql —— 服务名是 mariadb;
  • mysql / mysqladmin / mysqldump 命令 —— 新版提供 mariadb / mariadb-admin / mariadb-dump 一套新名字(老名字多数还留了别名,但新教程一律新名字);
  • 安装时让你设 root 密码 —— 现在安装过程根本不问,因为默认就是 socket 认证;
  • 用 mysql_install_db 初始化 —— 老命令,新版已移除,包安装时自动完成初始化。
自测三问(点开对答案) 1. 为什么 `mysql -u root -p` 在这台机器上登不进去?正确的登录方式是什么?(root 默认使用 **`unix_socket` 认证**,它不是靠密码、而是靠「你是不是系统 root」。正确方式:**`sudo mariadb`**) 2. 为什么网站程序不该用 root 连数据库?(**最小权限原则**:网站一旦被攻破,root 权限会连累整台数据库服务器;专用账号只授某一个库的权限,损失可控) 3. 建库时为什么写 `utf8mb4` 而不是 `utf8`?(MariaDB/MySQL 里的 **`utf8` 是不完整的旧编码**;`utf8mb4` 才是完整的 UTF-8,能正确存中文与 emoji。而且编码要在**建库、连接、页面 meta** 三处都对齐)

相关笔记