StartMVC开发手册

可以快速上手的开发文档

手册目录

数据查询

数据查询Fetch

可使用的方法有:get(),first(),value(),column(),find(),chunk()

查询多条数据get() 或many()

get($type), 如果$type为true时,不执行查询,直接返回构建的sql语句

// 获取多条记录
Db::table('test')->get();
// 生成SQL: "SELECT * FROM test"

// 获取指定字段的多条记录
Db::select('title')->table('pages')->where('id', 17)->get();
// 生成SQL: "SELECT title FROM pages WHERE id='17'"

// 获取指定字段的多条记录
Db::select('username')->table('users')->where('status', 1)->get();
// 生成SQL: "SELECT username FROM users WHERE status='1'"

获取单条数据 First()或 one() 别名

成功返回一维数据,失败返回null

$user = Db::table('users')->where('status', 1)->first();
// 结果:单个用户对象或数组

获取单一字段 Value

成功返回字段值对应的数值,失败返回null, 这个方法可以从一条数据中获取到单一字段的值

// 获取单个字段的值
$username = Db::table('users')->where('id', 1)->value('username');
// 结果:字符串 "admin"

按字段提取列数据column

获取单列数据:

// 获取所有用户名
$usernames = Db::table('users')->column('username');
// 结果: ['admin', 'user1', 'user2', ...]

构建键值对:

// 获取用户ID到邮箱的映射
$emails = Db::table('users')->column('email', 'id');
// 结果: [1 => 'admin@example.com', 2 => 'user1@example.com', ...]

用于选择框:

// 获取分类下拉框选项
$categories = Db::table('categories')->column('name', 'id');
// 在视图中使用
foreach ($categories as $id => $name) {
 echo "{$name}";
}

快速按主键查询find

方法有find(),findMany()

find($id) 通过主键查找单条记录,返回记录数组或 null

find($id, $key, true) 查找记录,找不到抛异常 ,返回记录数组

findMany($ids,$key) 通过主键数组查找多条记录 ,返回记录数组

findOrFail($id,$key) *已过时,使用 find($id, 'id', true)*,返回记录数组

基本用法

// 通过主键查找单条记录(默认找不到返回 null)
$user = $db->table('users')->find(1);

// 找不到会抛出异常(新方式)
try {
 $user = $db->table('users')->find(999, 'id', true); // 第三个参数控制是否抛异常
} catch (Exception $e) {
 echo "用户不存在";
}

// 兼容旧方式(已标记为过时)
try {
 $user = $db->table('users')->findOrFail(999);
} catch (Exception $e) {
 echo "用户不存在";
}

// 批量查找多条记录
$users = $db->table('users')->findMany([1, 2, 3, 4, 5]);

// 使用自定义主键字段
$user = $db->table('users')->find('admin@example.com', 'email');

// 自定义主键 + 抛异常
$user = $db->table('users')->find('admin@example.com', 'email', true);

与传统方式对比

// 传统方式
$user = $db->table('users')->where('id', 1)->first();
$users = $db->table('users')->in('id', [1,2,3])->get();

// 使用 find 方法(更简洁)
$user = $db->table('users')->find(1);
$users = $db->table('users')->findMany([1,2,3]);

分块遍历chunk()

chunk($count, $callback, $column = null),用于大数据量遍历:每批取 $count 行交给回调处理,再按推进列(默认主键 id)从上一批的最大值继续往后取,直到取完。每批只驻留 $count 行数据,内存占用与总行数无关。成功返回累计处理的行数。

参数说明:

  • $count 每批行数,必须大于 0
  • $callback 回调函数,签名为 function (array $rows),返回 false 可提前终止遍历
  • $column 推进列,默认 id,需为唯一且可排序的列(通常是自增主键)

基本用法:

// 分批处理 5 万条订单,每批 1000 行
$total = Db::table('orders')->where('status', 1)->chunk(1000, function ($rows) {
 foreach ($rows as $row) {
 // 处理每一行数据……
 }
});

echo "共处理 {$total} 行";

回调返回 false 提前终止:

Db::table('logs')->chunk(500, function ($rows) {
 foreach ($rows as $row) {
 if ($row['level'] === 'error') {
 return false; // 不再取下一批,遍历到此结束
 }
 }
});

自定义推进列(表没有 id 列时):

Db::table('user_stats')->chunk(200, function ($rows) {
 // ...
}, 'user_id');

边遍历边写库:

// 按主键推进,遍历过程中更新或删除已处理的行不会漏行、不会重复
Db::table('users')->where('synced', 0)->chunk(1000, function ($rows) {
 foreach ($rows as $row) {
 Db::table('users')->where('id', $row['id'])->update(['synced' => 1]);
 }
});

在模型上使用(自动带上软删除范围):

$userModel = $this->model('User');

$userModel->where('status', 1)->chunk(1000, function ($rows) {
 // 已软删除的记录不会被遍历到
});

与事务配合,任意一批出错即整体回滚:

Db::transaction(function () {
 Db::table('orders')->where('status', 1)->chunk(1000, function ($rows) {
 foreach ($rows as $row) {
 // 任意一步抛异常,整个事务回滚
 }
 });
});

事务的完整用法(嵌套事务、inTransaction()、手动模式等)见 事务操作及维护。

注意事项:

  1. 不要用 limit/offset 循环翻页来遍历全表:遍历过程中一旦有行被删除或插入,偏移量就会错位,导致已处理的行被跳过。chunk() 按推进列取值(WHERE id > 上一批最大值)推进,不受数据变动影响。
  2. 推进列必须唯一且可排序,默认主键 id;列名写错、或结果集里取不到该列时都会直接抛出异常,不会陷入死循环。
  3. 每批的 order / limit / offset 由 chunk() 内部接管(按推进列 ASC + LIMIT $count),调用方不需要也不应再设置。
  4. chunk() 是「分批查询」,遍历过程中可以安全地写库或开启事务;而游标(unbuffered 流式读取)在同一连接上边读边写会报错,边读边写的场景请用 chunk()。
  5. chunk() 自身不包事务。需要「整批失败整体回滚」时把它放进 Db::transaction(function () { ... }) 里;但事务会持有锁,超大数据量建议每批单独提交。

和分页的关系

chunk() 和分页解决的不是同一个问题,但两者的推进方式同源:chunk() 内部用的 WHERE 推进列 > 上一批最大值 ORDER BY 推进列 ASC LIMIT $count,把「页码」换成「上一页最后一行的主键值」,就是游标分页(keyset pagination)。

按需求选:

  • 要页码、要总数、能跳页(后台列表页)→ Db::table('t')->page($perPage, $page) 取数据,或 Model::paginate() 一次拿到数据与 total / total_pages / current_page / has_more,再用 Pagination 渲染页码。
  • 只要「下一页」、翻得很深(无限滚动、导出)→ 游标分页,前端传上一页最后一条的主键(如 ?last_id=xxx),不要传页码。代价是拿不到总数、不能跳页。
  • 要遍历全表做处理(刷数据、导报表)→ chunk(),它没有「页」的概念,只有「批」。

为什么深翻页别用 OFFSET:LIMIT 10 OFFSET 19000 要先扫描并丢弃前 19000 行,读取行数随页码线性增长;游标写法恒定只读 10 行。2 万行实测:OFFSET 19000 读取 19010 行,游标读取 10 行。

分页的完整用法见 分页类,模型层的 paginate() 见 模型。

获取查询次数queryCount

返回查询的数据条数.

// 执行一系列查询操作
$db = Db::table('users')->where('status', 1)->get();
$articles = Db::table('articles')->limit(10)->get();
$category = Db::table('categories')->where('id', 5)->first();

// 获取已执行的查询总数
$count = Db::queryCount();
echo "执行的查询总数: " . $count; // 输出: 执行的查询总数: 3

影响行数numRows()

返回数据影响条数.

// 查询示例
$users = Db::table('users')->where('status', 1)->get();
$affectedRows = Db::numRows();
echo "查询影响的行数: " . $affectedRows; // 例如: 查询影响的行数: 5

// 更新示例
Db::table('articles')->where('category_id', 3)->update(['status' => 0]);
$updatedRows = Db::numRows();
echo "更新影响的行数: " . $updatedRows; // 例如: 更新影响的行数: 8

// 删除示例
Db::table('comments')->where('post_id', 10)->delete();
$deletedRows = Db::numRows();
echo "删除影响的行数: " . $deletedRows; // 例如: 删除影响的行数: 12

最新插入的id号insertId()

使用此方法,当插入数据后,可以查询最后插入的数据主键值(比如id值)

// 插入单条数据
$data = [
 'title' => '新文章标题',
 'content' => '文章内容',
 'created_at' => date('Y-m-d H:i:s')
];

Db::table('articles')->insert($data);
$newArticleId = Db::insertId();
echo "新插入文章的ID: " . $newArticleId; // 例如: 新插入文章的ID: 25