swoole\table 底层是开链法哈希表,非数组或b树;size为哈希桶数且须为2的幂,item_size由列定义决定,内存独立生命周期,行锁为自旋锁,热点key易引发性能瓶颈。

Table 底层是开链法哈希表,不是数组或 B 树
很多人看到 $table->set('key', [...]) 就默认它是类似 PHP 数组的线性结构,其实不然。Swoole\Table 底层是用开链法(separate chaining)实现的哈希表,memory 是一块连续内存块,每个 slot 存一个 hash entry,冲突时从 pool 分配新节点挂到链表尾。这意味着:
- 查找性能取决于哈希分布和
conflict_proportion设置,超限后set()会直接失败,错误信息是"unable to allocate memory" -
size参数不是“预估行数”,而是哈希桶数量(即数组长度),实际能存多少行受冲突比例限制 -
mask = size - 1用于快速取模:哈希值& mask得到下标,所以size必须是 2 的幂次(如 1024、2048),否则创建失败且无明确报错
列定义的 size 参数决定字段内存占用,不是最大长度
$table->column('name', $table::TYPE_STRING, 64) 中的 64 是该字段固定分配的字节数,不是“最多存 64 字符”。它直接影响整行内存大小(item_size),进而影响总容量:
-
TYPE_INT的size只能是 1/2/4/8 字节;设成 3 或 5 会静默截断为 4 -
TYPE_STRING的size包含末尾\0,实际可用字符数是size - 1 -
TYPE_FLOAT固定占 8 字节,size参数被忽略 - 所有列加起来的
item_size越大,同样size下能存的行数越少——例如 1024 桶、每行 256 字节,总内存约 256KB;若每行涨到 1KB,就接近 1MB
Table 生命周期独立于 Server,不销毁会持续占内存
Swoole\Table 基于 mmap 创建共享内存段,它的生命周期完全由应用控制,和 Swoole\Http\Server 是否运行无关:
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 正常
server->shutdown()或kill -15后,Table 内存不会释放,除非显式调用$table->destroy() -
kill -9或崩溃时操作系统会回收,但这是兜底行为,不能依赖 - Worker 进程 reload 时,旧进程 Table 不会被自动迁移或同步,新 Worker 必须重新
create() - 多个 Worker 共享同一块 Table 需显式传入相同
key并配合ftok(),否则各自 mmap 独立内存——这也是线上误用导致“数据查不到”的常见原因
行锁是自旋锁,高并发写同 key 会明显拖慢性能
Table 所谓“无需加锁”是指用户不用写 lock/unlock,但底层对每行都有一把自旋锁(spinlock)。当多个协程/进程同时操作同一 key 时:
-
set()、incr()、decr()都会先尝试获取该行锁,失败则忙等(非阻塞等待) - 若业务中存在热点 key(比如计数器、全局配置),大量协程争抢同一行锁,CPU 使用率会飙升,吞吐反而下降
- 没有“锁超时”机制,也无排队策略,纯靠自旋 —— 这意味着在低配机器或高争用场景下,延迟毛刺非常明显
- 验证方式:压测时观察
top中 PHP 进程 CPU 是否持续 90%+,同时get()延迟突增,大概率是某 key 成了锁瓶颈
真正难的不是建表或读写,而是预估 size 和 item_size 的组合是否撑得住业务峰值,以及识别并隔离那些隐式的热点 key。这两点没想清楚,Table 很快就会从“性能加速器”变成“稳定性雷区”。










