Laravel从事务表计算用户信用余额速度慢[重复]

hjzp0vay  于 2022-12-27  发布在  其他
关注(0)|答案(1)|浏览(104)
    • 此问题在此处已有答案**:

MySQL SUM Query is extremely slow(3个答案)
2小时前关门了。
我正在Laravel 9项目中工作,用户可以购买"配额",然后在每次使用API时使用。但是,我需要每次检查用户的剩余配额,现在我的查询速度相当慢。
我有一个CreditTransaction模型,该模型将每个事务存储在一个表中,该表包含一个user_id和一个delta列,这些列可能为正,也可能为负,具体取决于它们是否购买了配额或使用了配额。然后,我对credit_transactionshasMany关系执行delta列的sum,这样就可以了。但是在查询时花费几秒钟来计算。

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\SoftDeletes;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable implements MustVerifyEmail
{
    use HasApiTokens, HasFactory, Notifiable, TwoFactorAuthenticatable, SoftDeletes;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'role',
        'first_name',
        'last_name',
        'email',
        'timezone',
        'password',
        'origin_source',
        'origin_source_other',
        'origin_campaign',
        'last_login_at',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
        'two_factor_recovery_codes',
        'two_factor_secret',
        'origin_campaign',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
        'last_login_at' => 'datetime',
    ];

    /**
     * Get the credit transactions for the user
     *
     * @return int
     */
    public function getCreditBalanceAttribute()
    {
        try {
            if (!$this->credit_transactions) {
                return 0;
            }

            $balance = $this->credit_transactions->sum('delta');

            if ($balance <= 0) {
                return 0;
            }

            return $balance;

        } catch (\Exception $err) { }

        return 0;
    }

    /**
     * Get the credit transactions for the user
     *
     * @return array
     */
    public function credit_transactions()
    {
        return $this->hasMany(CreditTransaction::class);
    }
}

然后,我可以执行以下操作来获取当前余额。

Auth::user()->credit_balance

我不知道如何最好地处理这个问题,因为这是基于120k行的表大小,这是相当小的,同样,我需要信用余额是准确和快速的,所以缓存15分钟是不是一个选项。
这是我的credit_transactions表:

Schema::create('credit_transactions', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->index();
    $table->foreignId('credit_type_id')->default(1)->index();
    $table->foreignUuid('message_id')->nullable()->index();
    $table->integer('delta')->index();
    $table->timestamps();
    $table->softDeletes();
});
rjee0c15

rjee0c151#

更新您的方法以将事务处理总和获取为:

/**
     * Get the credit transactions for the user
     *
     * @return int
     */
    public function getCreditBalanceAttribute()
    {
        try {
            // actually this code retrieves all user's transactions from DB 
            // and then calculate SUM

            //if (!$this->credit_transactions) {
            //    return 0;
            //}
            //$balance = $this->credit_transactions->sum('delta');
           
            // instead use this:
            // Ask database to calculate SUM of delta
            $balance = $this->credit_transactions()->sum('delta');

            if ($balance <= 0) {
                return 0;
            }

            return $balance;

        } catch (\Exception $err) { }

        return 0;
    }

在此查看更多信息-Laravel Eloquent Sum of relation's column
要更快地运行sum(delta),需要在user_id和delta列上创建多列索引。

CREATE INDEX credit_transactions_user_id_delta_idx ON credit_transactions(user_id, delta);

或者将其添加到您的Laravel迁移中:

$table->index(['user_id', 'delta']);

dbfiddle example(每个用户20 k个事务)sum仅用了7 ms。
是否可以更快地计算用户余额?
可以,但方法不同,创建一个存储实际用户余额的列,然后自动更新余额列,例如:

begin;
-- add 500 credits to user balance
UPDATE users SET balance = balance + 500;
-- query to log user transactions
-- INSERT INTO credit_transactions (your_column_list) VALUES (your_value_list);
commit;

另请参见Is this SQL safe enough to handle user balance transaction?

相关问题