读写分离
达到一个数据库做查询,另一个数据库连接做插入、更新和删除,laravel中实现这种读写分离非常简单,不管你用的事原生SQL,还是查询构建器,还是Eloquent ORM ,只要配置正确,合适的连接总是会被使用。
'mysql' => [
"read" =>[
"host"=>'192.168.1.1'
]
'write'=>[
'host'=>'196.168.1.2'
]
'driver' => 'mysql',
'database' => 'database',
'username'=>'root',
'password'=>'',
'charset'=>'urf8',
'collation'=>'utf8_unicode_ci',
'prefix' => '',
]
注意我们在配置数组中新增了两个键:read和write,这两个键对应值都有一个包含单个键“host”的数组,而其所映射的IP值分别就是读连接和写连接,读/写连接的其它数据库配置项都共用mysql的主数组配置。
如果我们想要覆盖主数组中的配置,只需要将相应配置项放到read和write数组中即可。192.168.1.1将被用作‘读’连接,而192.168.1.2将被用作‘写’连接。两个数据库连接的凭证(用户名/密码)、前缀、字符集以及其它配置将会共享mysql数组的设置,同理,如果不一样的话,分别在read或write数组中单独配置即可。
对于大部分应用来说都是读多写少,所以面对这种情况,如何配置多个读连接,一个写连接?可以这样做:
'mysql' => [
'driver'=>'mysql',
'read' =>[
'host'=>['193.168.1.1','194.168.1.1']
],
'write'=>[
'host'=>'196.168.1.2'
]
]
laravel 在读数据时会从提供的IP中随机选一个进行连接
注:目前读写分离仅支持单个写连接
sticky选项
sticky选项时一个可选的配置,可用于在当前请求生命周期内允许立即读取写入数据库的记录。如果sticky选项被启用并且一个写操作在当前生命周期内发生,则后续所有‘读’操作都会使用这个写连接(前提是同一个请求生命周期内),这样就可以确保同一个请求生命周期内写入的数据都可以立即被读取到,从而避免主从延迟导致的数据不一致,是否启用这一功能却决于你。
使用不同数据库连接
使用多个数据库连接的时候,可以通过DB门面上的connection方法访问不同连接。传递给connection方法的name对应配置文件config/database.php中设置的某个连接
$user = DB::connection('read')->select(...);
甚至还可以指定数据库和连接名,使用::分割
$users = DB::connection("mysql::read")->select(......);
你还可以使用连接实例上的getPdo方法访问底层原生的PDO实例
$pdo = DB::connect('read')->getPdo();
运行原生SQL查询
配置好数据库连接后,就可以使用DB门面来运行查询。DB门面为每种操作提供了相应方法:select,update,insert,delete和statement.
运行select查询
运行一个最基本的查询,可以使用DB门面的select方法
function index(){
$users = DB::select('select * from users where active = ?',[1]);
return view('user,index',['users'=>$users]);
}
传递给select方法的第一个参数是原生的SQL语句,第二个参数需要绑定到查询的参数绑定,通常,这些都是where子句约束中的值。参数绑定可以避免SQL注入攻击(输入参数校验由实现方控制,用户无法传递任意查询参数)。
select 方法以数组的形式返回结果集,数组中的每一个结果都是一个PHP stdclass对象
你可以像下面这样访问结果集
foreach($users as $user){
echo $user->name;
}
使用命名绑定:
除了使用?占位符来代表绑定外,还可以使用命名绑定来执行查询:
$result = DB::select("select * from users where id = :id ",['id'=>1]);
运行插入语句
使用DB门面的insert方法执行插入语句。和select一样,该方法将原生SQL语句作为第一个参数,将参数绑定作为第二个参数:
DB::insert('insert into users (id , name ) values (? , ?)' , [1,'学院军']);
运行更新语句
update方法用于更新数据库中已存在的记录,该方法返回受更新语句影响的行数:
$affected = DB::update('update users set votes=100 where name = ?' , ['学院军'] );
运行删除语句
delete方法用于删除数据库已存在的记录,和update一样,该语句返回被删除的行数
$deleted = DB::delete('delete from users');
运行一个通用语句
有些数据库语句不返回任何值,比如新增表,修改表,删除表等,对于这种类型的操作,可以使用DB门面的statement方法:
DB::statement('drop table users');
监听查询事件
public function boot(){
DB::listen(function $query){
//$query->sql
//$query->bindings
//$query->time
}
}
数据库事务
想要在一个数据库事务中运行一连串操作,可以使用DB门面的transaction方法,使用transaction方法时不需要手动回滚或提交:如果事务闭包中抛出异常,事务将会自动回滚;如果闭包执行成功,事务将会自动提交:
DB:transaction(function(){
DB::table('users')->update(['votes'=>1]);
DB::table('posts')->delete();
})
处理死锁
数据库死锁指的是两个或两个以上数据库操作相互依赖,一方需要等待另一方退出才能获取资源,但是没有一方提前退出,就会造成死锁,数据库事务容易造成的一个副作用就是死锁。为此transaction方法接收一个可选参数作为第二个参数,用于定义死锁发生时事务的最大重试次数。如果尝试次数超出指定值,会抛出异常:
DB::transaction(function(){
DB::table('users')->update(['votes'=>1]);
DB::table('posts')->delete();
}, 5 )
手动使用事务
如果你想要手动开启事务从而对回滚和提交有更好的控制,可以使用DB门面的beginTransaction方法:
DB::beginTransaction();
你可以通过roolBack方法回滚事务
DB::rollBack();
最后,你可以通过commit方法提交事务:
DB::commit();
注意:使用DB门面的事务方法还可以用于控制查询构建器和Eloquent ORM的事务。