MySQL 事务与锁:理解隔离级别和死锁的实战指南

前言

事务和锁是 MySQL 最核心也最容易出问题的部分。很多人写了很多年 SQL,却还是搞不清隔离级别和死锁。本文用最直白的方式讲清楚。

一、事务的 ACID

事务就是一组操作,要么全部成功,要么全部失败。四个特性:

  • A 原子性:一组操作是一个整体,不能拆分
  • C 一致性:事务前后,数据库从一个一致状态到另一个一致状态
  • I 隔离性:多个事务之间互不干扰
  • D 持久性:事务提交后,数据就永久保存了

二、并发问题

多个事务同时操作数据,会出什么问题?

1. 脏读

事务 A 读到了事务 B 还没提交的数据。

事务 A:修改了余额为 1000,但还没提交
事务 B:读到了余额 1000
事务 A:回滚了,余额变回 500
事务 B:拿着 1000 的错误数据继续操作

2. 不可重复读

事务 A 两次读同一行数据,结果不一样。

事务 A:第一次读余额是 500
事务 B:修改了余额为 1000,提交了
事务 A:第二次读余额是 1000

3. 幻读

事务 A 两次查询,结果集的行数不一样。

事务 A:第一次查询 age > 20 的用户,有 10 条
事务 B:插入了一条 age = 25 的用户,提交了
事务 A:第二次查询 age > 20 的用户,有 11 条

三、四种隔离级别

MySQL 用隔离级别来解决这些并发问题:

隔离级别 脏读 不可重复读 幻读
读未提交(READ UNCOMMITTED) 会 会 会
读提交(READ COMMITTED) 不会 会 会
可重复读(REPEATABLE READ) 不会 不会 会(InnoDB 解决了)
串行化(SERIALIZABLE) 不会 不会 不会

MySQL 默认是可重复读(REPEATABLE READ),InnoDB 在这个级别下用间隙锁解决了幻读。

查看当前隔离级别

SELECT @@tx_isolation;

修改隔离级别

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

四、锁的类型

1. 共享锁(S 锁)

读锁,多个事务可以同时持有。

SELECT ... LOCK IN SHARE MODE;

2. 排他锁(X 锁)

写锁,只有一个事务能持有。

SELECT ... FOR UPDATE;

3. 表锁 vs 行锁

  • 表锁:锁住整张表,开销小,但并发低
  • 行锁:锁住一行数据,开销大,但并发高

五、死锁

什么是死锁

两个事务互相等待对方释放锁。

事务 A:锁住了 id = 1,等待 id = 2
事务 B:锁住了 id = 2,等待 id = 1

两个事务都卡住了,谁也不让谁。

怎么避免死锁

  1. 按相同顺序访问表和行:所有事务都按 id 从小到大操作
  2. 事务尽量短:事务越长,锁持有时间越长
  3. 降低隔离级别:隔离级别越低,锁越少
  4. 加索引:没有索引会走表锁,更容易死锁

查看死锁

-- 查看最近一次死锁
SHOW ENGINE INNODB STATUS;

六、实战:转账

-- 开启事务
BEGIN;

-- 扣款(加行锁)
UPDATE account SET balance = balance - 100 WHERE id = 1;

-- 检查余额
SELECT balance FROM account WHERE id = 1;

-- 如果余额不够,回滚
-- ROLLBACK;

-- 加钱
UPDATE account SET balance = balance + 100 WHERE id = 2;

-- 提交
COMMIT;

七、常见坑

  1. 事务太长:锁持有时间太长,容易死锁
  2. 没有索引:行锁变表锁,并发暴跌
  3. 隔离级别太高:用串行化,性能很差
  4. 忘记提交:事务一直开着,锁一直占着

总结

MySQL 事务与锁的核心思路:

  1. 事务保证 ACID,一组操作要么全成功要么全失败
  2. 隔离级别越高越安全,但性能越差
  3. MySQL 默认可重复读,InnoDB 解决了幻读
  4. 行锁比表锁并发高,但一定要有索引
  5. 按相同顺序操作,能避免大部分死锁

搞懂这些,你对 MySQL 的理解就超过 80% 的开发者了。


emer 发布于  2026-10-4 10:11 

PHP 错误与异常处理:从 die 到优雅的错误处理

前言

很多 PHP 新手处理错误就是 die() 或者 exit(),页面直接白屏,用户体验极差。本文讲清楚 PHP 的错误和异常处理机制,帮你写出更健壮的代码。

一、错误 vs 异常

很多人搞不清错误和异常的区别:

  • 错误:PHP 本身的问题,比如语法错误、调用不存在的函数
  • 异常:程序逻辑上的问题,比如数据库连不上、文件不存在

PHP 7 以后,很多错误也变成了异常,可以用 try-catch 捕获。

二、错误级别

PHP 有很多错误级别:

E_ERROR      致命错误,脚本停止运行
E_WARNING    警告,脚本继续运行
E_NOTICE     注意,一般不影响运行
E_PARSE      语法解析错误
E_DEPRECATED 过时提醒

开发环境和生产环境的配置不一样:

# 开发环境:显示所有错误
display_errors = On
error_reporting = E_ALL

# 生产环境:不显示错误,只记录日志
display_errors = Off
log_errors = On
error_reporting = E_ALL

三、异常处理基础

try-catch-finally

<?php
try {
    // 可能出错的代码
    $db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
} catch (PDOException $e) {
    // 捕获异常
    echo "数据库连接失败:" . $e->getMessage();
} finally {
    // 不管有没有异常都会执行
    echo "执行完毕";
}

抛出异常

<?php
function divide($a, $b)
{
    if ($b == 0) {
        throw new Exception("除数不能为 0");
    }
    return $a / $b;
}

try {
    echo divide(10, 0);
} catch (Exception $e) {
    echo "错误:" . $e->getMessage();
}

四、自定义异常类

<?php
class DbException extends Exception
{
    public function errorMessage()
    {
        return "数据库错误:" . $this->getMessage();
    }
}

class UserException extends Exception
{
    public function errorMessage()
    {
        return "用户错误:" . $this->getMessage();
    }
}

// 使用
try {
    if (!$user) {
        throw new UserException("用户不存在");
    }
} catch (UserException $e) {
    echo $e->errorMessage();
} catch (DbException $e) {
    echo $e->errorMessage();
} catch (Exception $e) {
    echo "其他错误:" . $e->getMessage();
}

五、错误处理器

把 PHP 错误转换成异常,统一处理:

<?php
// 自定义错误处理器
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
    // 把错误转换成异常抛出
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});

try {
    // 这个本来是 warning,现在会被捕获
    $result = 10 / 0;
} catch (ErrorException $e) {
    echo "错误:" . $e->getMessage();
}

六、异常处理器

兜底处理,最后一道防线:

<?php
// 自定义异常处理器
set_exception_handler(function ($e) {
    echo "未捕获的异常:" . $e->getMessage();
    // 记录日志
    error_log($e->getMessage());
});

// 没有 try-catch 的异常会走这里
throw new Exception("爆炸了");

七、常见错误处理方式对比

方式 问题
die() / exit() 页面白屏,用户体验差
echo 错误信息 代码混在一起,不好维护
错误码返回 调用方需要判断错误码
异常处理 代码清晰,错误统一管理

八、最佳实践

  1. 生产环境不要显示错误:会暴露服务器信息
  2. 一定要记录日志:方便排查问题
  3. 用异常处理替代 die():代码更优雅
  4. 自定义异常类:区分不同类型的错误
  5. 兜底异常处理器:防止未捕获的异常白屏

九、日志记录

<?php
try {
    // 可能出错的代码
} catch (Exception $e) {
    // 记录日志
    error_log(
        date('Y-m-d H:i:s') . " 错误:" . $e->getMessage() . "\n",
        3,
        '/var/log/php/error.log'
    );

    // 给用户友好提示
    echo "系统繁忙,请稍后再试";
}

总结

PHP 错误处理的核心思路:

  1. 开发环境显示错误,生产环境隐藏错误
  2. 用 try-catch 捕获异常,不要用 die()
  3. 自定义异常类,区分不同错误类型
  4. 设置错误处理器,把错误转成异常
  5. 一定要记录日志,方便排查问题

掌握这些,你的 PHP 代码会健壮很多。


emer 发布于  2026-10-4 10:09 

PHP 设计模式:单例、工厂、观察者实战详解

前言

设计模式不是八股文,它是前人踩坑踩出来的最佳实践。PHP 开发中最常用的三个设计模式:单例、工厂、观察者。本文用最直白的方式讲清楚。

一、单例模式(Singleton)

什么时候用

全局只需要一个实例的对象,比如:

  • 数据库连接
  • 配置管理
  • 日志对象

代码实现

<?php
class Database
{
    private static $instance = null;
    private $conn;

    // 构造函数私有化,防止外部 new
    private function __construct()
    {
        $this->conn = mysqli_connect('localhost', 'user', 'pass', 'db');
    }

    // 禁止克隆
    private function __clone() {}

    // 禁止反序列化
    public function __wakeup()
    {
        throw new Exception("不能反序列化单例");
    }

    public static function getInstance()
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function getConnection()
    {
        return $this->conn;
    }
}

// 使用
$db = Database::getInstance();
$conn = $db->getConnection();

为什么要私有化构造函数

如果构造函数是 public 的,外面就能 new Database(),那就不是单例了。

二、工厂模式(Factory)

什么时候用

需要根据不同条件创建不同对象的时候,比如:

  • 根据支付类型创建不同的支付对象(微信、支付宝、银行卡)
  • 根据数据库类型创建不同的数据库对象(MySQL、Redis)

简单工厂

<?php
interface Payment
{
    public function pay($amount);
}

class WechatPay implements Payment
{
    public function pay($amount)
    {
        echo "微信支付 {$amount} 元";
    }
}

class Alipay implements Payment
{
    public function pay($amount)
    {
        echo "支付宝支付 {$amount} 元";
    }
}

class PaymentFactory
{
    public static function make($type)
    {
        switch ($type) {
            case 'wechat':
                return new WechatPay();
            case 'alipay':
                return new Alipay();
            default:
                throw new Exception("不支持的支付方式");
        }
    }
}

// 使用
$pay = PaymentFactory::make('wechat');
$pay->pay(100);

工厂模式的好处

  1. 解耦:客户端不需要知道具体怎么创建对象
  2. 易扩展:新增支付方式只需要加一个类和工厂里的一个 case
  3. 统一管理:所有对象的创建都在工厂里,方便维护

三、观察者模式(Observer)

什么时候用

一个对象状态改变时,需要通知多个其他对象,比如:

  • 用户注册后:发邮件、发短信、送积分
  • 订单状态变更后:通知仓库、通知物流、通知客服

代码实现

<?php
// 事件接口
interface Observer
{
    public function update($eventData);
}

// 事件主体
class Event
{
    private $observers = [];

    public function attach(Observer $observer)
    {
        $this->observers[] = $observer;
    }

    public function trigger($eventData)
    {
        foreach ($this->observers as $observer) {
            $observer->update($eventData);
        }
    }
}

// 具体观察者
class SendEmail implements Observer
{
    public function update($eventData)
    {
        echo "发送邮件给 {$eventData['email']}\n";
    }
}

class SendSms implements Observer
{
    public function update($eventData)
    {
        echo "发送短信给 {$eventData['phone']}\n";
    }
}

class GivePoints implements Observer
{
    public function update($eventData)
    {
        echo "给用户送 100 积分\n";
    }
}

// 使用
$event = new Event();
$event->attach(new SendEmail());
$event->attach(new SendSms());
$event->attach(new GivePoints());

// 用户注册,触发事件
$event->trigger([
    'email' => 'user@example.com',
    'phone' => '13800138000'
]);

观察者模式的好处

  1. 解耦:事件主体不需要知道谁在监听
  2. 易扩展:新增监听者只需要加一个类,不需要改原来的代码
  3. 符合开闭原则:对扩展开放,对修改关闭

四、什么时候用什么模式

场景 用什么模式
全局只需要一个实例 单例
根据条件创建不同对象 工厂
一个动作触发多个后续操作 观察者

五、常见坑

  1. 单例用太多:单例用多了会变成全局变量,代码难维护
  2. 工厂过度设计:简单逻辑就别硬上工厂,会增加复杂度
  3. 观察者滥用:所有操作都做成观察者,调试会很痛苦

总结

设计模式是工具,不是目的。

  • 单例:保证全局只有一个实例
  • 工厂:统一管理对象创建
  • 观察者:一个事件,多个响应

用对了让代码更清晰,用错了让代码更复杂。记住:先解决问题,再考虑模式。


emer 发布于  2026-10-4 10:08 

API 设计规范:RESTful 风格的最佳实践指南

前言

一个好的 API 设计,能让前后端协作效率翻倍;一个烂的 API,能让所有人都痛苦不堪。本文总结 RESTful API 设计的最佳实践,帮你写出规范、好用的接口。

一、URL 设计

1. 用名词,不用动词

# 好的
GET    /users        # 获取用户列表
POST   /users        # 创建用户
GET    /users/123    # 获取单个用户
PUT    /users/123    # 更新用户
DELETE /users/123    # 删除用户

# 不好的
GET    /getUsers
POST   /createUser
GET    /getUserById?id=123

2. 层级要清晰

# 获取某篇文章的评论
GET /users/123/articles/456/comments

3. 用复数名词

# 好的
/users

# 不好的
/user

二、HTTP 方法语义

方法 语义 幂等 安全
GET 获取资源 是 是
POST 创建资源 否 否
PUT 更新完整资源 是 否
PATCH 更新部分资源 否 否
DELETE 删除资源 是 否

幂等:同一个请求发多次,结果一样。
安全:请求不会修改服务器数据。

三、HTTP 状态码

200 OK                # 成功
201 Created           # 创建成功
204 No Content        # 删除成功,无返回内容

400 Bad Request       # 参数错误
401 Unauthorized      # 未登录
403 Forbidden         # 无权限
404 Not Found         # 资源不存在
429 Too Many Requests # 请求太频繁

500 Internal Server Error # 服务器错误

四、统一响应格式

成功响应

{
  "code": 0,
  "message": "success",
  "data": {
    "id": 123,
    "name": "张三"
  }
}

列表响应

{
  "code": 0,
  "message": "success",
  "data": {
    "list": [...],
    "total": 100,
    "page": 1,
    "page_size": 20
  }
}

错误响应

{
  "code": 10001,
  "message": "用户名或密码错误",
  "data": null
}

五、参数设计

1. 查询参数用 query string

GET /users?page=1&page_size=20&status=active

2. 创建/更新用 body

POST /users
{
  "name": "张三",
  "email": "zhangsan@example.com"
}

3. 路径参数用 path

GET /users/123/articles/456

六、分页、排序、过滤

# 分页
GET /users?page=1&page_size=20

# 排序
GET /users?sort=created_at&order=desc

# 过滤
GET /users?status=active&role=admin

七、版本控制

API 要加版本号,方便升级不影响老用户。

# 在 URL 里加版本
/api/v1/users
/api/v2/users

# 或者在 Header 里加版本
Accept: application/vnd.myapi.v1+json

八、认证与授权

# 请求头带 Token
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

不要把 token 放在 URL 里,会泄露。

九、接口文档

好的 API 一定要有文档:

  • Swagger / OpenAPI:自动生成接口文档
  • Postman:方便调试和分享
  • 示例:每个接口都要有请求和响应示例

十、常见坑

  1. GET 请求带 body:不规范,浏览器和代理可能会忽略
  2. 状态码乱用:别什么错都返回 200
  3. 返回格式不统一:有的接口直接返回数据,有的包一层 code
  4. 命名不规范:一会儿驼峰,一会儿下划线
  5. 接口粒度太粗:一个接口干所有事,灵活性很差

总结

RESTful API 设计的核心思路:

  1. URL 用名词,用 HTTP 方法表达操作
  2. 状态码要正确,别什么都返回 200
  3. 响应格式统一,成功失败都一个结构
  4. 参数位置要合理,查询用 query,操作用 body
  5. 一定要有文档,不然没人会用你的接口

掌握这些,你的 API 设计水平就能超过 80% 的后端开发者了。


emer 发布于  2026-10-4 10:06 

HTTPS 原理详解:从证书到握手的完整流程

前言

HTTPS 现在已经是网站标配了,但很多人只知道"HTTPS 更安全",却不知道它到底是怎么安全的。本文用最通俗的方式,带你完整理解 HTTPS 的工作原理。

一、HTTP 为什么不安全

HTTP 是明文传输的,就像你在明信片上写信,邮局的每个人都能看到内容。

具体来说,HTTP 有三个问题:

  1. 窃听风险:中间人可以看到你传输的所有内容
  2. 篡改风险:中间人可以修改你传输的内容
  3. 冒充风险:中间人可以冒充服务器欺骗你

HTTPS 就是为了解决这三个问题而生的。

二、HTTPS = HTTP + SSL/TLS

HTTPS 本质上就是在 HTTP 和 TCP 之间加了一层 SSL/TLS 协议。

TLS 做了三件事:

  1. 加密:数据加密传输,中间人看不到内容
  2. 完整性校验:数据被篡改了能发现
  3. 身份认证:确认你连接的是真的服务器

三、数字证书

HTTPS 信任的基础是数字证书。

你可以把数字证书理解成身份证:

  • 谁发的:CA 机构(证书颁发机构)
  • 证明什么:这个域名属于谁
  • 有效期:身份证有有效期,证书也有
  • 怎么验证:CA 用私钥签名,浏览器用公钥验证

常见的免费证书:Let's Encrypt

四、TLS 握手流程

TLS 握手是 HTTPS 最核心的部分,用大白话讲一遍:

第一步:Client Hello

客户端说:"你好,我支持这些加密算法,这是我的随机数 A。"

Client → Server: 
- TLS 版本
- 支持的加密算法列表
- 随机数 A

第二步:Server Hello

服务器说:"好的,我们用这个加密算法,这是我的随机数 B,这是我的证书。"

Server → Client:
- 选择的加密算法
- 随机数 B
- 数字证书(包含服务器公钥)

第三步:客户端验证证书

客户端做几件事:

  1. 证书是不是可信 CA 签的?
  2. 证书过期了没?
  3. 证书上的域名和当前访问的域名一致吗?

验证通过,才继续。

第四步:生成会话密钥

客户端用服务器公钥加密一个"预主密钥"发给服务器。

Client → Server:
- 预主密钥(用服务器公钥加密)

服务器用自己的私钥解密,拿到预主密钥。

然后客户端和服务器都用:

  • 随机数 A
  • 随机数 B
  • 预主密钥

这三个东西,计算出会话密钥(Session Key)。

第五步:开始加密通信

双方都有了会话密钥,之后的所有通信都用这个密钥加密。

Client ↔ Server:
- 用会话密钥加密的数据

五、为什么用非对称加密交换密钥,对称加密传数据

这是很多人困惑的点:

  • 非对称加密(公钥私钥):安全但慢
  • 对称加密(同一个密钥):快但不安全

HTTPS 的设计很巧妙:

  1. 先用非对称加密安全地交换会话密钥(只交换一次)
  2. 然后用对称加密快速传输数据(所有通信都用这个密钥)

既保证了安全,又保证了性能。

六、证书链

一张证书不是孤立的,它背后有一条证书链:

根证书(Root CA)
  └─ 中间证书(Intermediate CA)
       └─ 服务器证书(你的网站)

浏览器信任根证书,根证书签中间证书,中间证书签你的服务器证书。这样一层层验证下来,就信任了你的服务器证书。

七、常见问题

1. 为什么有的网站 HTTPS 显示"不安全"

通常是因为:

  • 证书过期了
  • 证书域名不匹配
  • 证书是自签名的,不是可信 CA 签发的
  • 页面里混了 HTTP 的资源

2. HTTPS 一定安全吗?

不一定。如果:

  • 你电脑上的根证书被篡改了
  • CA 机构被攻破了
  • 你主动信任了恶意证书

那 HTTPS 也会不安全。

3. HTTPS 会影响性能吗?

会有一点影响,主要在握手阶段。但现在 TLS 1.3 已经把握手优化到 1-RTT,加上会话复用,性能影响很小了。

总结

HTTPS 的核心思路:

  1. 用非对称加密安全交换会话密钥
  2. 用对称加密快速传输数据
  3. 用数字证书验证服务器身份
  4. 用 MAC 校验数据完整性

搞懂这四点,HTTPS 的原理你就掌握了一大半。


emer 发布于  2026-10-4 10:05 

Git 工作流:团队协作的最佳实践指南

前言

Git 是每个程序员都必须掌握的工具,但很多团队用着用着就乱了:分支满天飞、提交记录看不懂、合并冲突天天有。本文介绍一套简单实用的 Git 工作流,让团队协作井井有条。

一、分支模型

推荐使用简化版的 Git Flow,核心分支只有两个:

  • main / master:主分支,永远是可发布状态
  • develop:开发分支,日常开发都在这上面

临时分支:

  • feature/xxx:功能分支,从 develop 拉出来
  • bugfix/xxx:修复分支,从 develop 拉出来
  • release/xxx:发布分支,从 develop 拉出来准备上线
  • hotfix/xxx:紧急修复分支,从 main 拉出来修线上 bug

二、日常开发流程

1. 拉最新代码

git checkout develop
git pull origin develop

2. 创建功能分支

git checkout -b feature/user-login

3. 开发并提交

# 写代码...

# 添加文件
git add .

# 提交(commit message 要清晰)
git commit -m "feat: 添加用户登录页面"

commit message 规范:

  • feat: 新功能
  • fix: 修复 bug
  • docs: 文档修改
  • style: 代码格式调整
  • refactor: 重构
  • chore: 构建流程或工具变动

4. 推送到远程

git push origin feature/user-login

5. 发起 Pull Request / Merge Request

在 Git 平台(GitHub/GitLab/Gitee)上发起 PR,把 feature 分支合并到 develop 分支。

6. 代码评审

同事 review 代码,提出修改意见,修改后再合并。

7. 合并后删除分支

git checkout develop
git pull origin develop
git branch -d feature/user-login
git push origin --delete feature/user-login

三、发布流程

1. 从 develop 拉发布分支

git checkout -b release/1.2.0

2. 修复小问题,准备发布

在发布分支上只做 bug 修复,不加新功能。

3. 合并到 main,打 tag

git checkout main
git merge release/1.2.0
git tag -a v1.2.0 -m "版本 1.2.0"
git push origin main --tags

4. 合并回 develop

git checkout develop
git merge release/1.2.0
git push origin develop

四、线上紧急修复

1. 从 main 拉 hotfix 分支

git checkout main
git pull origin main
git checkout -b hotfix/login-bug

2. 修复 bug,合并回 main 和 develop

# 修复...

git checkout main
git merge hotfix/login-bug
git tag -a v1.2.1 -m "修复登录 bug"
git push origin main --tags

# 合并回 develop
git checkout develop
git merge hotfix/login-bug
git push origin develop

五、实用技巧

1. 撤销提交

# 撤销最近一次提交,保留代码改动
git reset --soft HEAD~1

# 撤销最近一次提交,丢弃代码改动(慎用)
git reset --hard HEAD~1

2. 暂存当前工作

# 临时保存当前工作
git stash

# 恢复
git stash pop

3. 查看历史

# 简洁的图形化提交历史
git log --oneline --graph --all

4. 变基保持提交干净

# 把 feature 分支的提交变基到最新的 develop 上
git checkout feature/user-login
git rebase develop

六、常见坑

  1. 不要在 main 上直接开发:所有改动都要通过 PR 合并
  2. commit 不要太大:一个功能一个 commit,方便回滚
  3. 不要 force push 到公共分支:会搞乱别人的代码
  4. 定期同步 develop:每天至少 pull 一次最新代码

总结

Git 工作流的核心思路:

  1. main 永远可发布,develop 是集成分支
  2. 功能分支从 develop 拉,修完合并回 develop
  3. 发布时从 develop 拉 release,合并到 main 打 tag
  4. 线上 bug 从 main 拉 hotfix,修完合并回 main 和 develop

这套流程简单实用,小团队用起来刚刚好。


emer 发布于  2026-10-4 10:03 

Docker 入门:从容器到 Compose 的实战教程

前言

Docker 现在已经是后端开发的标配工具了。很多人听说过 Docker,但不知道它到底是什么、怎么用。本文用最通俗的方式,带你从零开始掌握 Docker 的核心用法。

一、Docker 是什么

简单来说,Docker 就是把你的应用和它依赖的所有东西(代码、运行时、系统库、配置文件)打包到一个"集装箱"里,然后这个集装箱可以在任何机器上运行。

和虚拟机的区别:虚拟机是模拟一整套操作系统,Docker 容器是共享宿主机的内核,所以更轻量、启动更快。

二、核心概念

  • 镜像(Image):相当于模板,只读的,用来创建容器
  • 容器(Container):镜像运行起来的实例,可以创建、启动、停止、删除
  • Dockerfile:用来构建镜像的脚本文件
  • 仓库(Registry):存放镜像的地方,比如 Docker Hub

三、常用命令

1. 镜像操作

# 拉取镜像
docker pull nginx:latest

# 查看本地镜像
docker images

# 删除镜像
docker rmi nginx:latest

2. 容器操作

# 运行容器
docker run -d -p 80:80 --name my-nginx nginx

# 查看运行中的容器
docker ps

# 查看所有容器(包括停止的)
docker ps -a

# 停止容器
docker stop my-nginx

# 启动容器
docker start my-nginx

# 删除容器
docker rm my-nginx

# 查看容器日志
docker logs my-nginx

# 进入容器内部
docker exec -it my-nginx /bin/bash

关键参数说明:

  • -d:后台运行
  • -p 80:80:端口映射,宿主机端口:容器端口
  • --name:给容器起个名字

四、Dockerfile 实战

写一个简单的 PHP 应用镜像:

# 基于 PHP 官方镜像
FROM php:7.4-fpm

# 设置工作目录
WORKDIR /var/www/html

# 复制项目文件
COPY . .

# 安装依赖
RUN docker-php-ext-install pdo_mysql

# 暴露端口
EXPOSE 9000

# 启动命令
CMD ["php-fpm"]

构建镜像:

docker build -t my-php-app .

运行容器:

docker run -d -p 9000:9000 my-php-app

五、Docker Compose 实战

当你需要同时运行多个容器(比如 PHP + MySQL + Nginx),手动一个个启动太麻烦了。Docker Compose 就是用来编排多个容器的。

创建 docker-compose.yml 文件:

version: '3'

services:
  # PHP 应用
  app:
    build: .
    container_name: php-app
    volumes:
      - ./:/var/www/html
    networks:
      - app-network

  # MySQL 数据库
  db:
    image: mysql:5.7
    container_name: mysql-db
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: myapp
    volumes:
      - dbdata:/var/lib/mysql
    networks:
      - app-network

  # Nginx 反向代理
  nginx:
    image: nginx:alpine
    container_name: nginx-web
    ports:
      - "80:80"
    volumes:
      - ./:/var/www/html
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - app
    networks:
      - app-network

volumes:
  dbdata:

networks:
  app-network:
    driver: bridge

启动所有服务:

docker-compose up -d

停止所有服务:

docker-compose down

六、常见问题

1. 容器启动后立刻退出

通常是因为启动命令有问题。用 docker logs 查看日志:

docker logs container_name

2. 数据持久化

容器删除后数据就没了,需要用 volumes 来持久化:

docker run -v /host/path:/container/path mysql

3. 进入容器排查问题

docker exec -it container_name /bin/bash

总结

Docker 的核心思路:

  1. 镜像:打包应用和依赖
  2. 容器:镜像的运行实例
  3. Dockerfile:构建镜像的脚本
  4. Docker Compose:多容器编排

掌握这些,你就可以用 Docker 来部署你的 Web 应用了。Docker 最大的好处就是"一次构建,到处运行",再也不会出现"我电脑上是好的"这种问题了。


emer 发布于  2026-10-4 10:02 

Redis 缓存策略:穿透、击穿、雪崩的完整解决方案

前言

Redis 缓存是高并发系统的标配,但用不好反而会出大问题。缓存穿透、击穿、雪崩这三个经典问题,几乎每个后端开发都踩过坑。本文用实战的方式,带你彻底搞懂这三个问题的原理和解决方案。

一、缓存穿透

什么是穿透

查询一个数据库里根本不存在的数据,缓存里也没有,每次请求都打到数据库上。这就是缓存穿透。

比如有人恶意请求 id = -1 的用户数据,数据库里永远查不到,缓存里也永远没有,每次请求都要查一次数据库。

解决方案

1. 缓存空值

function getUserById($id) {
    $user = $redis->get("user:{$id}");

    if ($user === false) {
        // 数据库查询
        $user = $db->query("SELECT * FROM users WHERE id = ?", [$id])->fetch();

        if ($user) {
            $redis->setex("user:{$id}", 3600, json_encode($user));
        } else {
            // 空值也缓存,短时间过期
            $redis->setex("user:{$id}", 60, 'NULL');
        }
    }

    return $user === 'NULL' ? null : $user;
}

优点:简单直接
缺点:如果恶意请求的 id 非常多,会占用大量缓存空间

2. 布隆过滤器

在缓存前面加一层布隆过滤器,先判断这个 id 是否存在,不存在直接返回。

// 初始化布隆过滤器,把所有存在的 id 放进去
$bloomFilter = new RedisBloomFilter($redis);
$bloomFilter->add($allUserIds);

function getUserById($id) {
    // 先查布隆过滤器,不存在直接返回
    if (!$bloomFilter->exists($id)) {
        return null;
    }

    // 再走缓存查询...
}

优点:占用空间小,效率高
缺点:有一定误判率,实现复杂

二、缓存击穿

什么是击穿

某个热点 key 突然过期了,这一瞬间大量请求同时打到数据库上,就像被击穿了一样。

和穿透的区别:穿透是数据不存在,击穿是数据存在但缓存过期了。

解决方案

1. 互斥锁

function getHotProduct($id) {
    $product = $redis->get("product:{$id}");

    if ($product === false) {
        // 尝试获取锁
        $lockKey = "lock:product:{$id}";
        $isLocked = $redis->set($lockKey, 1, ['NX', 'EX' => 10]);

        if ($isLocked) {
            // 拿到锁,查数据库
            $product = $db->query("SELECT * FROM products WHERE id = ?", [$id])->fetch();
            $redis->setex("product:{$id}", 3600, json_encode($product));
            $redis->del($lockKey);
        } else {
            // 没拿到锁,等一会再查缓存
            usleep(10000);
            return getHotProduct($id);
        }
    }

    return $product;
}

优点:保证只有一个请求查数据库
缺点:实现复杂,有死锁风险

2. 热点数据永不过期

对真正的热点数据,直接设置永不过期,由后台异步更新。

// 热点数据不设过期时间,后台定时更新
$redis->set("hot_product:{$id}", json_encode($product));

三、缓存雪崩

什么是雪崩

大量缓存 key 在同一时间集体过期,或者 Redis 整个宕机了,所有请求瞬间全部打到数据库上,数据库直接被压垮。

和击穿的区别:击穿是一个热点 key,雪崩是大量 key 同时失效。

解决方案

1. 过期时间加随机值

// 不要所有 key 都设成 3600 秒过期
$ttl = 3600 + rand(0, 600);  // 在 1 小时基础上加 0-10 分钟随机值
$redis->setex("product:{$id}", $ttl, json_encode($product));

这样就不会出现大量 key 同一时间过期的情况。

2. Redis 集群高可用

部署 Redis 主从 + 哨兵,或者 Redis Cluster,确保 Redis 本身不会单点故障。

3. 服务降级限流

当数据库压力过大时,主动限流,返回兜底数据。

// 简单的降级逻辑
if ($redis->ping() === false) {
    // Redis 挂了,直接返回默认数据
    return getDefaultProduct();
}

四、三种问题对比

问题 原因 特点 解决方案
穿透 数据不存在 查不到的数据一直被请求 缓存空值 / 布隆过滤器
击穿 热点 key 过期 单个热点 key 瞬间失效 互斥锁 / 永不过期
雪崩 大量 key 同时过期 大面积缓存失效 随机过期时间 / 集群 / 限流

总结

缓存三件套的核心思路:

  1. 穿透:不让不存在的数据反复打数据库
  2. 击穿:热点数据过期时只让一个请求去查数据库
  3. 雪崩:让缓存失效时间错开,同时保证 Redis 高可用

搞懂这三个问题,你的缓存方案才算真正入门了。


emer 发布于  2026-10-4 09:58 

Nginx 配置实战:反向代理、负载均衡与缓存

前言

Nginx 是目前最流行的 Web 服务器和反向代理软件。很多人会写简单的配置,但对反向代理、负载均衡、缓存这些高级用法并不熟悉。本文带你从零开始,掌握 Nginx 的核心配置技巧。

一、反向代理基础

反向代理是 Nginx 最常用的功能之一。用户请求 Nginx,Nginx 再把请求转发给后端应用服务器。

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

关键说明:

  • proxy_pass:后端服务地址
  • proxy_set_header:传递真实的客户端信息给后端,否则后端拿到的都是 Nginx 的信息

二、负载均衡

当后端有多台服务器时,可以用 Nginx 做负载均衡。

1. 轮询(默认)

upstream backend {
    server 127.0.0.1:8080;
    server 127.0.0.1:8081;
    server 127.0.0.1:8082;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend;
    }
}

2. 加权轮询

upstream backend {
    server 127.0.0.1:8080 weight=3;
    server 127.0.0.1:8081 weight=2;
    server 127.0.0.1:8082 weight=1;
}

weight 值越大,分配的请求越多。适合配置不同的服务器。

3. ip_hash 会话保持

upstream backend {
    ip_hash;
    server 127.0.0.1:8080;
    server 127.0.0.1:8081;
}

同一个 IP 的请求总是分配到同一台后端服务器,适合有 session 的场景。

三、缓存配置

Nginx 缓存可以大幅减轻后端压力,提升响应速度。

1. 配置缓存路径

http {
    # 定义缓存路径和参数
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;

    server {
        listen 80;
        server_name example.com;

        location / {
            proxy_pass http://backend;
            proxy_cache my_cache;
            proxy_cache_valid 200 302 10m;
            proxy_cache_valid 404 1m;
            proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
            add_header X-Cache-Status $upstream_cache_status;
        }
    }
}

关键参数说明:

  • keys_zone:缓存共享内存区大小
  • max_size:缓存最大占用空间
  • inactive:多久没访问就自动清理
  • proxy_cache_valid:不同状态码的缓存时间
  • X-Cache-Status:查看是否命中缓存(HIT/MISS/BYPASS)

2. 不缓存特定请求

location /admin/ {
    proxy_pass http://backend;
    proxy_cache off;  # 后台页面不缓存
}

四、HTTPS 配置

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        proxy_pass http://127.0.0.1:8080;
    }
}

# HTTP 跳转 HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

五、常见问题排查

1. 502 Bad Gateway

通常是后端服务挂了或者端口不对:

# 检查后端服务是否在运行
netstat -tlnp | grep 8080

# 查看 Nginx 错误日志
tail -f /var/log/nginx/error.log

2. 上传文件大小限制

http {
    client_max_body_size 50M;
}

3. 超时设置

location / {
    proxy_connect_timeout 60s;
    proxy_read_timeout 60s;
    proxy_send_timeout 60s;
}

总结

Nginx 配置的核心思路:

  1. 反向代理:把请求转发给后端应用
  2. 负载均衡:多台后端服务器分担流量
  3. 缓存:静态内容缓存,减少后端压力
  4. HTTPS:加密传输,安全第一

掌握这些配置,你就能应对大部分 Web 服务器场景了。Nginx 配置文件改完记得执行 nginx -t 检查语法,然后 nginx -s reload 重载生效。


emer 发布于  2026-10-4 09:56 

PHP 性能优化:从代码到部署的完整指南

前言

PHP 性能优化是每个 PHP 开发者都绕不开的话题。很多人觉得 PHP 性能差,但实际上通过合理的优化,PHP 应用完全可以达到很高的性能水平。本文从代码层面到部署层面,带你完整梳理 PHP 性能优化的思路。

一、代码层面优化

1. 避免在循环中做数据库查询

// 慢:循环中查询数据库
foreach ($users as $user) {
    $order = $db->query("SELECT * FROM orders WHERE user_id = {$user['id']}");
}

// 快:一次性查询,然后用数组映射
$userIds = array_column($users, 'id');
$orders = $db->query("SELECT * FROM orders WHERE user_id IN (" . implode(',', $userIds) . ")")->fetchAll();
$orderMap = array_column($orders, null, 'user_id');

2. 用生成器处理大数据

// 慢:一次性加载所有数据到内存
$users = $db->query("SELECT * FROM big_table")->fetchAll();

// 快:用生成器逐行处理
function getUsers() {
    $stmt = $pdo->query("SELECT * FROM big_table");
    while ($row = $stmt->fetch()) {
        yield $row;
    }
}

3. 字符串拼接 vs implode

// 慢:频繁拼接字符串
$str = '';
foreach ($items as $item) {
    $str .= $item . ',';
}

// 快:用数组收集最后拼接
$parts = [];
foreach ($items as $item) {
    $parts[] = $item;
}
$str = implode(',', $parts);

二、PHP 配置优化

1. OPcache 必开

OPcache 是 PHP 性能提升最大的一个扩展,没有之一。

; php.ini
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.fast_shutdown=1

开启 OPcache 后,PHP 脚本会被编译成字节码缓存起来,避免每次请求都重新编译。性能提升通常在 2-5 倍。

2. 合理设置内存限制

memory_limit = 256M

不要设置太大,也不要太小。根据你的应用实际情况调整。

三、数据库优化

1. 开启慢查询日志

slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1

找出慢查询,然后针对性优化。

2. 使用持久化连接要谨慎

// 不一定更快,可能反而更慢
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass', [
    PDO::ATTR_PERSISTENT => true
]);

持久化连接在高并发场景下可能导致连接耗尽,要根据实际情况测试。

四、部署层面优化

1. 使用 PHP-FPM 优化

; php-fpm/www.conf
pm = dynamic
pm.max_children = 50
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30

根据服务器内存调整 max_children,每个 PHP-FPM 进程大约占用 20-40MB 内存。

2. Nginx 开启 gzip

gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1000;

3. 静态文件 CDN 加速

把图片、CSS、JS 等静态资源放到 CDN 上,减轻服务器压力。

五、性能监控工具

  • Xdebug:代码性能分析,找出瓶颈函数
  • Blackfire:更专业的性能分析工具
  • New Relic:APM 应用性能监控
  • ab / wrk:压力测试工具

总结

PHP 性能优化的核心思路:

  1. 先找出瓶颈在哪里,不要盲目优化
  2. 代码层面:减少数据库查询、减少内存占用
  3. 配置层面:OPcache 必开
  4. 部署层面:PHP-FPM、Nginx、数据库都要调优
  5. 用工具量化优化效果

性能优化是一个持续迭代的过程,不要指望一次性优化到位。每次做一点,慢慢积累。


emer 发布于  2026-10-4 09:55 

Linux 服务器安全加固:10个必须做的基础配置

前言

刚装完的 Linux 服务器就像没锁门的房子,任何人都能进来。本文整理了 10 个最基础但最重要的安全加固配置,帮你把门锁上。

1. 修改 SSH 端口,禁止 root 登录

默认 22 端口是黑客扫描的首选目标,改个端口能挡掉 90% 的自动扫描。

# 编辑 sshd 配置
vi /etc/ssh/sshd_config

# 修改端口
Port 22222

# 禁止 root 登录
PermitRootLogin no

# 重启 sshd
systemctl restart sshd

注意:改完端口后,记得在防火墙和安全组里放行新端口,否则你自己也进不去了。

2. 使用 SSH 密钥登录,禁用密码登录

密码再复杂也扛不住暴力破解,密钥登录才是正道。

# 本地生成密钥对(在你自己的电脑上执行)
ssh-keygen -t ed25519 -C "your_email@example.com"

# 把公钥传到服务器
ssh-copy-id -p 22222 user@your_server_ip

# 服务器上禁用密码登录
vi /etc/ssh/sshd_config
PasswordAuthentication no

3. 配置防火墙,只开必要端口

# 只开 SSH、HTTP、HTTPS
ufw default deny incoming
ufw default allow outgoing
ufw allow 22222/tcp   # SSH
ufw allow 80/tcp      # HTTP
ufw allow 443/tcp     # HTTPS
ufw enable

4. 自动安全更新

# Ubuntu/Debian
apt install unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades

# CentOS/RHEL
dnf install dnf-automatic
systemctl enable --now dnf-automatic.timer

5. 创建普通用户,配置 sudo

永远不要用 root 跑日常操作,创建一个普通用户,需要时用 sudo 提权。

# 创建用户
adduser deploy

# 加入 sudo 组
usermod -aG sudo deploy

6. 禁用不必要的服务

# 查看正在运行的服务
systemctl list-unit-files --type=service --state=enabled

# 禁用不需要的服务
systemctl disable --now avahi-daemon
systemctl disable --now cups

7. 配置 fail2ban 防暴力破解

apt install fail2ban

# 配置 sshd 防护
vi /etc/fail2ban/jail.local

[sshd]
enabled = true
port = 22222
maxretry = 3
bantime = 3600

systemctl restart fail2ban

8. 设置密码策略

# 编辑 pwquality 配置
vi /etc/security/pwquality.conf

minlen = 12          # 最小长度
dcredit = -1         # 至少1个数字
ucredit = -1         # 至少1个大写字母
lcredit = -1         # 至少1个小写字母
ocredit = -1         # 至少1个特殊字符

9. 监控日志,定期检查

# 查看登录失败记录
grep "Failed password" /var/log/auth.log

# 查看当前登录用户
w

# 查看最近登录记录
last -n 20

10. 定期备份,备份再备份

安全加固做得再好,也不如有一份能快速恢复的备份。

# 简单的文件备份脚本
tar -czf backup_$(date +%Y%m%d).tar.gz /var/www/

把备份文件同步到异地存储,别只放在同一台服务器上。

总结

这 10 条配置不需要高深的技术,但每一条都能实实在在地降低服务器被攻破的风险。建议新服务器装好后,第一件事就是把这 10 条全部过一遍。

安全不是一次性的工作,而是一个持续的过程。定期更新系统、监控日志、检查异常,才能真正把服务器守好。


emer 发布于  2026-10-4 09:53 

MySQL 索引优化实战:从 Explain 分析到查询性能翻倍

前言

数据库查询慢是 Web 开发中最常见的性能问题之一。而绝大多数慢查询都可以通过合理使用索引来解决。本文将从 Explain 执行计划入手,带你一步步掌握 MySQL 索引优化的实战技巧。

一、认识 Explain 执行计划

要优化查询,首先得知道 MySQL 是怎么执行你的 SQL 的。EXPLAIN 命令可以展示 MySQL 执行查询的详细步骤:

EXPLAIN SELECT * FROM orders WHERE user_id = 10086 AND status = 'paid';

重点关注以下几个字段:

  • type:访问类型,从好到差依次为 const > eq_ref > ref > range > index > ALL。出现 ALL 就是全表扫描,必须优化。
  • key:实际使用的索引。如果为 NULL,说明没有用到索引。
  • rows:预估需要扫描的行数。这个值越大越慢。
  • Extra:额外信息。Using filesort 和 Using temporary 都是危险信号。

二、最左前缀原则

联合索引 (a, b, c) 的匹配规则:

-- 用到索引 a,b,c
SELECT * FROM t WHERE a = 1 AND b = 2 AND c = 3;

-- 用到索引 a,b
SELECT * FROM t WHERE a = 1 AND b = 2;

-- 只用到索引 a(中间断了)
SELECT * FROM t WHERE a = 1 AND c = 3;

-- 完全用不到索引
SELECT * FROM t WHERE b = 2 AND c = 3;

实战要点:把等值查询的列放在前面,范围查询的列放在后面。

三、常见索引失效场景

1. 对索引列使用函数或运算

-- 失效:对索引列用了函数
SELECT * FROM users WHERE DATE(created_at) = '2026-01-01';

-- 有效:改成范围查询
SELECT * FROM users WHERE created_at >= '2026-01-01' AND created_at < '2026-01-02';

2. 隐式类型转换

-- 失效:phone 是 varchar,用数字查
SELECT * FROM users WHERE phone = 13800138000;

-- 有效:加引号
SELECT * FROM users WHERE phone = '13800138000';

3. LIKE 以通配符开头

-- 失效
SELECT * FROM users WHERE name LIKE '%张%';

-- 有效
SELECT * FROM users WHERE name LIKE '张%';

四、实战优化案例

问题 SQL(扫描 10 万行,耗时 1.2 秒):

SELECT id, title, created_at FROM articles
WHERE category_id = 5 AND status = 'published'
ORDER BY created_at DESC LIMIT 20;

Explain 结果:type=index,rows=120000,Using filesort。

优化方案:创建联合索引

ALTER TABLE articles ADD INDEX idx_cat_status_time (category_id, status, created_at DESC);

优化后:type=ref,rows=20,Extra 无 filesort。查询时间从 1.2 秒降到 0.005 秒。

五、索引不是越多越好

索引虽然能加速查询,但也有代价:

  1. 写入变慢:每次 INSERT/UPDATE 都要维护索引
  2. 占用空间:索引也是要存磁盘的
  3. 优化器选错:索引太多可能让优化器犯迷糊

建议:单表索引数量控制在 5 个以内,避免重复索引和冗余索引。

总结

MySQL 索引优化的核心思路:

  1. 用 EXPLAIN 看执行计划,找到慢的原因
  2. 遵循最左前缀原则设计联合索引
  3. 避免常见的索引失效写法
  4. 定期清理无用索引

掌握这些技巧,你就能解决 80% 以上的慢查询问题。


emer 发布于  2026-10-4 09:52 

PHP 8.3 新特性详解:类型类获取、动态常量获取和更多实用功能

前言

PHP 8.3 于 2023 年 11 月正式发布,作为 PHP 8.x 系列的第四个版本,它带来了一系列实用的语法增强和性能优化。本文将深入介绍 PHP 8.3 中最值得关注的几个新特性。

1. 类型类获取(Typed class constants)

在 PHP 8.3 之前,类常量无法声明类型。从 PHP 8.3 开始,类常量可以指定类型,这有助于提高代码的类型安全性。

class Foo
{
    const string BAR = 'bar';
    const int MAX_COUNT = 100;
}

声明了类型的常量必须与声明的类型匹配,否则会抛出 TypeError。支持的类型包括:标量类型、array、object、static、self、parent、类名/接口名以及 iterable。

2. 动态类常量获取

PHP 8.3 新增了 constant() 函数的增强,允许通过表达式动态获取类常量:

class Status
{
    const DRAFT = 'draft';
    const PUBLISHED = 'published';
    const ARCHIVED = 'archived';
}

$status = 'PUBLISHED';
echo constant(Status::class . "::$status"); // 输出: published

这在需要根据变量动态选择常量时非常有用,避免了大量的 switch 或 match 分支。

3. #[\Override] 注解

PHP 8.3 引入了 #[\Override] 注解,用于显式标记一个方法是重写父类的方法:

abstract class Controller
{
    abstract public function handle(): void;
}

class UserController extends Controller
{
    #[\Override]
    public function handle(): void
    {
        echo "Handling user request...";
    }
}

如果使用了 #[\Override] 注解,但方法实际上并没有重写任何父类方法(比如方法名拼错了),PHP 会在编译时报错。这在大型项目中非常有价值,可以防止因为方法名不一致导致的隐蔽 bug。

4. Readonly 深拷贝

PHP 8.3 改进了 readonly 属性的处理。在 PHP 8.2 中,readonly 属性不能通过克隆来修改。PHP 8.3 允许在克隆时修改 readonly 属性:

class Point
{
    public function __construct(
        public readonly int $x,
        public readonly int $y
    ) {}
}

$p1 = new Point(10, 20);
$p2 = clone $p1;
$p2->x = 30; // PHP 8.3 中允许,PHP 8.2 会报错

这个特性在值对象模式中特别有用,可以创建修改后的副本而不影响原始对象。

5. 其他实用改进

json_validate() 函数

PHP 8.3 新增了 json_validate() 函数,用于验证 JSON 字符串是否有效,而不需要解码:

$json = '{"name": "Alice", "age": 30}';
if (json_validate($json)) {
    echo "Valid JSON";
}

这比 json_decode() 更高效,因为它不需要分配内存来存储解码后的结果。

随机浮点数

PHP 8.3 的 Random\Randomizer 类新增了 getFloat() 方法:

$r = new Random\Randomizer();
$float = $r->getFloat(0, 10, \Random\IntervalBoundary::ClosedOpen);

总结

PHP 8.3 虽然不是一个大版本更新,但它带来的每一个新特性都直击开发者的日常痛点。类型类常量和 #[\Override] 注解增强了代码的可维护性,动态常量获取简化了代码逻辑,readonly 深拷贝则让值对象模式更加实用。

建议正在使用 PHP 8.x 的项目尽快升级到 8.3,既能获得这些新特性的好处,也能享受性能上的小幅提升。


emer 发布于  2026-10-4 09:48 

命令行模式下 PHP Redis 为什么会提示 Class Redis not found?

命令行模式下PHPRedis无法使用在命令行模式下运行PHP程序时,遇到ClassRedisnotfound...


emer 发布于  2025-1-10 22:16 

为什么我的 MySQL 数据转换为 JSON 后斜杠变成了反斜杠?

JSON编码后斜杠异常的问题解答在从MySQL读取数据并将其转换为JSON时,您发现产生的JSON中反斜杠"/"...


emer 发布于  2025-1-10 22:16 

Lithe 中的播种者:轻松填充您的数据库

在应用程序开发方面,拥有可用的测试数据至关重要。lithe中的播种器提供了一种简单有效的方法来使用初始数据或测试数据填充数据库,使您能够专注于应用程序逻辑。在这篇文章中,我们将探索如何在lithe中创建和使用播种器。什么是播种机?seeders是负责自动将数据插入数据库的类。当您需要虚拟数据来测试功能和应用程序行为时,它们在开发过程中特别有用。使用播种器,您可以确保您的应用程序拥有必要的数据,而无需手动插入。创建播种机在lithe中,您可以使用make:seeder命令轻松创建播种机。此命令会在


emer 发布于  2025-1-10 22:16 

如何将 WordPress 评论功能集成到自定义 PHP 页面?

WordPress评论功能的提取提取WordPress评论功能并将其集成到自定义PHP...


emer 发布于  2025-1-10 22:16 

如何使用 PHP 去除字符串中的 HTML 标记?

PHP中清除字符串中的HTML标记对于以下需求:给定一个字符串$str='ttttgg....


emer 发布于  2025-1-10 22:16 

如何通过程序模拟人工点击网页按钮,获取内存中的加密超链接?

通过程序模拟人为跳转页面问题:如何通过程序模拟人工点击网页按钮,在内存中经过跳转获得加密后的超链接...


emer 发布于  2025-1-10 22:16 

Lithe 开发的最佳实践

Lithe开发的最佳实践当使用Lithe进行开发时,一些实践可以显着提高代码的效率和可维护性。这里有一些有价值的技巧,可以帮助您充分利用这个PHP微框架。1.在路由上使用控制器在路由中使用控制器的主要优点之一是,这允许控制器内的代码包含在应用程序中,并且仅在访问相应的路由时才执行。这种方法不仅提高了代码的可读性,还优化了应用程序的性能,因为它减少了每个请求中需要加载的代码量。使用Lithe的make:controller命令生成控制器时,您可以从这种结构中受益,因为业务逻辑被隔离并更有效地组织。


emer 发布于  2025-1-10 22:16