🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## 数据库字符集的选择 MySQL在5.5.3之后增加了这个utf8mb4的编码,mb4就是most bytes 4的意思,专门用来兼容四字节的unicode。好在utf8mb4是utf8的超集,除了将编码改为utf8mb4外不需要做其他转换。当然,为了节省空间,一般情况下使用utf8也就够了。 本来3字节的utf8已经可以覆盖这个世界上的所有语言,直到emoji等符号的出现,让字符集大大扩展,导致utf8开始出现一些四字节的符号。 所以我们建议使用数据库的编码为`utf8mb4`,排序方式为:`utf8mb4_unicode_ci`。 ## `utf8mb4_unicode_ci`与`utf8mb4_general_ci`如何选择 字符除了需要存储,还需要排序或比较大小,涉及到与编码字符集对应的 排序字符集(collation)。ut8mb4对应的排序字符集常用的有`utf8mb4_unicode_ci`、`utf8mb4_general_ci`。 主要从排序准确性和性能两方面看: * 准确性 `utf8mb4_unicode_ci`是基于标准的Unicode来排序和比较,能够在各种语言之间精确排序。 `utf8mb4_general_ci`没有实现Unicode排序规则,在遇到某些特殊语言或字符是,排序结果可能不是所期望的。 * 性能 `utf8mb4_general_ci`在比较和排序的时候更快。 `utf8mb4_unicode_ci`在特殊情况下,Unicode排序规则为了能够处理特殊字符的情况,实现了略微复杂的排序算法。 我个人推荐是`utf8mb4_unicode_ci`,将来 8.0 里也极有可能使用变为默认的规则。相比选择哪一种collation,使用者应该更关心字符集与排序规则在db里要统一就好。 ## 转换到utf8mb4 如果表定义和连接字符集都是utf8,那么直接在你的表上执行: ~~~ ALTER TABLE tbl_name CONVERT TO CHARACTER SET utf8mb4; ~~~ ## 数据库配置 直接在`database.php`数据库配置文件中按如下方式定义即可。 ~~~ return [ 'default' => 'mysql', 'connections' => [ 'mysql' => [ // 数据库类型 'type' => 'mysql', // 服务器地址 'hostname' => '127.0.0.1', // 数据库名 'database' => 'thinkphp', // 数据库用户名 'username' => 'root', // 数据库密码 'password' => '', // 数据库连接端口 'hostport' => '', // 数据库连接参数 'params' => [], // 数据库编码默认采用utf8 'charset' => 'utf8mb4', // 数据库表前缀 'prefix' => 'my_', ], ], ]; ~~~ 也可在本地再配置一份数据库,用于开发阶段使用。见`thinkphp6`根目录下面的`.env`文件。 ~~~ APP_DEBUG = true [APP] DEFAULT_TIMEZONE = Asia/Shanghai [DATABASE] TYPE = mysql HOSTNAME = 127.0.0.1 DATABASE = project USERNAME = root PASSWORD = 123456 HOSTPORT = 3306 CHARSET = utf8mb4 DEBUG = true [LANG] default_lang = zh-cn ~~~ ## 查询数据 所有的查询都采用静态方法,可以使用`Db`类或者模型类完成查询构造器操作,也就是`think\facade\Db`,通过门面对象`think\facade\Db`进行静态方法调用。以下列举常用的方法。 | 查询方法 | 作用描述 | | --- | --- | | table | 指定查询数据表 | | field | 指定查询字段 | | where | 指定查询条件 | | order | 指定结果排序 | | limit | 指定查询结果数 | | find | 查询一条记录 | | select | 查询数据集 | | insert | 写入数据 | | update | 更新数据 | | delete | 删除数据 | ## 数据库架构设计 使用框架开发应用,一般不需要直接操作数据库,而是通过框架封装好的数据库中间层对数据库进行操作。这样的好处主要有两个:一是简化数据库操作,二是做到跨数据库的一致性。这种设计的中间层通常称之为数据库访问抽象层,简称数据访问层(`DAL`),`ThinkPHP6`的数据访问层是基于PHP内置的`PDO`对象实现。一般抽象层本身并不直接操作数据库,而是通过驱动来实现具体的数据库操作。 `ThinkPHP6`的数据库设计相比之前版本更加合理,数据访问层划分的更细化,把数据访问对象分成了连接器、查询器、生成器等多个对象,并通过数据库访问入口类统一调用,分工更明确,各司其职。 ### 查询单行 查询单行数据使用`find`方法: ~~~ // table方法必须指定完整的数据表名 Db::table('think_user')->where('id', 1)->find(); // 如果设置了数据表前缀(prefix)参数的话 也可以使用 Db::name('user')->where('id', 1)->find(); ~~~ > 即使满足条件的数据有多个,`find`查询也只会返回一条数据。你可以使用`order`排序来决定返回哪一条数据。 最终生成的SQL语句如下: ~~~ SELECT * FROM `think_user` WHERE `id` = 1 LIMIT 1 ~~~ > `find`方法查询结果不存在,返回`null`,否则返回结果数组。 > 如果希望查询数据不存在的时候返回空数组,可以使用 ~~~ // table方法必须指定完整的数据表名 Db::table('think_user')->where('id', 1)->findOrEmpty(); ~~~ 如果希望在没有找到数据后抛出异常可以使用 ~~~ Db::table('think_user')->where('id', 1)->findOrFail(); ~~~ 如果没有查找到数据,则会抛出一个`think\db\exception\DataNotFoundException`异常。 ### 查询多行数据 查询多行数据使用`select`方法: ~~~ $list = Db::table('think_user')->where('status', 1)->select(); foreach ($list as $user) { echo $user['name']; } ~~~ 最终生成的SQL语句是: ~~~ SELECT * FROM `think_user` WHERE `status` = 1 ~~~ `select`方法查询结果是一个数据集对象(`think\Collection`),如果需要转换为纯粹的二维数组,可以使用toArray()方法。 ~~~ $list = Db::table('think_user')->where('status', 1)->select()->toArray(); foreach ($list as $user) { echo $user['name']; } ~~~ 如果希望查询数据不存在的时候返回空数组,可以使用`select`。`select`方法查询结果不存在,返回返回空数组(`[]`)。 > 如果你的数据表没有设置表前缀的话,那么`name`和`table`方法是一样的。 > **注意!如果要判断数据集是否为空,不能使用`empty`判断**,必须使用数据集对象的`isEmpty`方法判断,如下。 ~~~ $users = Db::name('user')->select(); if($users->isEmpty()){ echo '数据集为空'; } ~~~ ### 查询单元格的值 ~~~ // 返回某个单元格的值 Db::table('think_user')->where('id', 1)->value('name'); ~~~ > `value`方法查询结果不存在,返回`null`。 ### 查询若干列的值 ~~~ // 返回一列,类型为数组 Db::table('think_user')->where('status',1)->column('name'); // 指定id字段的值作为索引 Db::table('think_user')->where('status',1)->column('name', 'id'); ~~~ 如果要返回完整数据,并且添加一个索引值的话,可以使用 ~~~ // 指定id字段的值作为索引 返回所有数据 Db::table('think_user')->where('status',1)->column('*','id'); ~~~ > `column`方法查询结果不存在,返回空数组(`[]`)。 ## 新增数据 可以使用`insert`方法向数据库明确新增一条数据 ~~~ $data = ['foo' => 'bar', 'bar' => 'foo']; Db::name('user')->insert($data); ~~~ > `insert`方法添加数据成功返回添加成功的条数,通常情况返回 1。 如果你的数据表里面没有`foo`或者`bar`字段,那么就会抛出异常。如果不希望抛出异常,可以通过下面的方法关闭严格模式,丢弃不存在的字段: ~~~ $data = ['foo' => 'bar', 'bar' => 'foo']; Db::name('user')->strict(false)->insert($data); ~~~ 如果你的数据表采用了自增主键,并且添加数据后如果需要返回新增数据的自增主键,可以使用`insertGetId`方法新增数据并返回主键值: ~~~ $userId = Db::name('user')->insertGetId($data); ~~~ ## 添加多条数据 添加多条数据直接使用`insertAll`方法传入需要添加的数据(通常是二维数组)即可。 ~~~ $data = [ ['foo' => 'bar', 'bar' => 'foo'], ['foo' => 'bar1', 'bar' => 'foo1'], ['foo' => 'bar2', 'bar' => 'foo2'] ]; Db::name('user')->insertAll($data); ~~~ > `insertAll`方法添加数据成功返回添加成功的条数 ### 更新数据 使用`update`方法更新数据,数据最好明确包含主键数据。 ~~~ Db::name('user')->update(['id' => 1, 'name' => 'thinkphp']); ~~~ 生成的SQL语句: ~~~ UPDATE `think_user` SET `name`='thinkphp' WHERE `id` = 1 ~~~ 更新的数据如果不包含主键,则必须指定更新条件,例如: ~~~ Db::name('user') ->where('id' ,1) ->update(['name' => 'thinkphp']); ~~~ > `update`方法返回影响数据的条数,没修改任何数据则返回 0。 如果要更新的数据需要使用`SQL`函数,可以使用下面的方式: ~~~ Db::name('user') ->where('id', 1) ->update([ 'name' => Db::raw('UPPER(name)'), 'score' => Db::raw('score-3'), 'read_time' => Db::raw('read_time+1') ]); ~~~ ## 删除数据 ~~~ // 根据主键删除 Db::table('think_user')->delete(1); Db::table('think_user')->delete([1, 2, 3]); // 条件删除 Db::table('think_user')->where('id', 1)->delete(); Db::table('think_user')->where('id', '<', 10)->delete(); ~~~ 最终生成的SQL语句是: ~~~ DELETE FROM `think_user` WHERE `id` = 1 DELETE FROM `think_user` WHERE `id` IN (1,2,3) DELETE FROM `think_user` WHERE `id` = 1 DELETE FROM `think_user` WHERE `id` < 10 ~~~ > `delete`方法返回影响数据的条数,没有删除任何数据返回 0。 出于安全考虑,如果不带任何条件调用`delete`方法会提示错误,如果你确实需要删除所有数据,可以使用: ~~~ // 无条件删除所有数据 Db::name('user')->delete(true); ~~~ ## 使用原生SQL语句 ### `query`方法 `query`方法用于执行`SQL`查询操作,和`select`方法一样返回查询结果数据集(数组)。 ~~~ Db::query('select * from think_user where status=1'); ~~~ ### Db::raw 方法 ~~~ $conditions = [['hit', '>', 0], ['', 'exp', Db::raw('CHAR_LENGTH(id)=3')]]; $data = Db::name('sys_words')->where($conditions)->limit(2)->select(); 位运算的使用方法: ~~~ $map = \[\['', 'exp', Db::raw('role & (1<< 3 )')\]\]; ~~~ 注意 `CONCAT_WS `与 `CONCAT`对 `NULL`的区别: ~~~ $map\[\] = \['', 'exp', Db::raw('CONCAT\_WS(",",`GCDM`,`XMDM`) LIKE "%' . $param\['code'\] . '%"')\]; ~~~ // view 方法比较 特殊 $map = [Db::raw('CHAR_LENGTH(id)=3')]; //必须是数组 $data = Db::view('sys_words', '*')->where('hit', '>', 0)->where($map)->limit(2)->select(); ~~~ ### `execute`方法 `execute`用于更新和写入数据的sql操作,如果数据非法或者查询错误则返回`false`,否则返回影响的记录数。 ~~~ Db::execute("update think_user set name='thinkphp' where status=1"); ~~~ ### `query`方法 ~~~ Db::query('select * from users where id=1'); ~~~ > 对数据表的CURD操作,除了`select`和存储过程调用使用`query`方法之外,其它的操作都使用`execute`方法。 ### 参数绑定 支持在原生查询的时候使用参数绑定,包括问号占位符或者命名占位符,例如: ~~~ Db::query("select * from think_user where id=? AND status=?", [8, 1]); // 命名绑定 Db::execute("update think_user set name=:name where status=:status", ['name' => 'thinkphp', 'status' => 1]); ~~~ > 注意不支持对表名使用参数绑定 ### SQL调试 ~~~ Db::name('users')->getLastSql(); ~~~ 在模型操作中 ,为了更好的查明错误,经常需要查看下最近使用的SQL语句,可以用`getLastsql`方法来输出上次执行的sql语句。例如: ~~~ User::get(1); echo User::getLastSql(); ~~~ 输出结果是`SELECT * FROM 'think_user' WHERE 'id' = 1` > `getLastSql`方法只能获取最后执行的`SQL`记录。 也可以使用`fetchSql`方法直接返回当前的查询SQL而不执行,例如: ~~~ echo User::fetchSql(true)->find(1); ~~~ 输出的结果是一样的。