unset() 不是释放内存而是断开变量名与值的绑定,zval 是否回收取决于引用计数是否归零;资源类型除外,会立即销毁;操作数组、对象属性、超全局变量等有诸多限制和陷阱。

unset() 不是“删内存”,而是断开变量名和值的绑定
PHP 的 unset() 并不直接释放内存,它只是把变量名从当前作用域的符号表里移除。背后那个值(zval)是否真被回收,取决于还有没有其他变量或数据结构在引用它。
比如 $a = ['x' => 1]; $b = $a; 后调用 unset($a),$b 依然能正常访问原数组——因为 zval 的引用计数还没降到 0。
- 只有当某个 zval 的引用计数归零,且不是循环引用时,PHP 才会在下次垃圾回收周期中真正释放它
- 对资源类型(如文件句柄、MySQL 连接)调用
unset(),会立即触发资源销毁逻辑(这是例外) -
unset($arr['key'])删除的是数组元素,不是整个数组;若该元素值本身是引用,要注意副作用
unset() 在 foreach 中修改数组容易出错
边遍历边用 unset() 删数组元素,极易跳过后续项或触发 Notice: Undefined index。这是因为 PHP 的数组内部指针和键顺序在删除后不会自动重排,但 foreach 仍按原始迭代顺序推进。
常见错误写法:
$arr = [0 => 'a', 1 => 'b', 2 => 'c'];
foreach ($arr as $k => $v) {
if ($v === 'b') unset($arr[$k]);
}结果是 $arr 剩下 [0 => 'a', 2 => 'c'],但 foreach 已经走完三轮,中间没报错却漏处理了逻辑。
- 安全做法:先收集要删的键,再统一
unset(),或改用array_filter() - 对象属性上不能直接
unset($obj->prop)(除非类定义了__unset()) - 全局变量在函数内用
unset()只影响局部作用域,不影响 $GLOBALS 或外部
unset() 对超全局变量($_GET、$_POST 等)无效
直接 unset($_GET['id']) 看似成功,但实际只是断开了当前作用域对 $_GET 数组中该元素的访问,原始请求数据没变,且后续 include 或函数内仍可能读到旧值——因为超全局变量是引用传递的特殊结构。
立即学习“PHP免费学习笔记(深入)”;
更关键的是:某些 SAPI(如 CLI)或框架(如 Laravel)会提前把 $_GET 冻结为只读副本,此时 unset() 甚至会静默失败。
- 想“过滤”输入,应该用
filter_input()或显式复制再操作,例如$safe = $_GET; unset($safe['token']); -
unset($_SESSION)会销毁整个 session 变量数组,但不会调用 session_destroy(),session 文件还活着 - 在 register_globals = on 的古早环境里,
unset($foo)也不会自动清除同名全局变量(已淘汰,仅作兼容提醒)
性能与调试时的典型误判
有人以为频繁 unset() 能“节省内存”,其实多数时候适得其反:它增加符号表操作开销,且可能延缓真正的 GC 触发时机。尤其在长生命周期脚本(如 PHP-FPM worker)中,盲目 unset 大数组反而让内存占用曲线更难预测。
调试时用 var_dump($var) 看不到已 unset() 的变量,但 get_defined_vars() 仍可能列出它(如果存在引用或作用域未退出)。
- 判断变量是否存在,优先用
isset()或array_key_exists(),别靠unset()后再var_dump()验证 - 在析构函数(
__destruct())里调用unset($this->prop)没意义,对象即将销毁,zval 引用计数自然清零 - OPcache 启用时,
unset()不影响已编译的 opcode,但会影响运行时的变量状态,这点常被忽略
unset(),而是 zval 的引用关系有没有彻底切断。











