【问题标题】:How can I make an update statement faster which involves around 300,000 rows?如何更快地制作涉及大约 300,000 行的更新语句?
【发布时间】:2019-08-24 12:12:15
【问题描述】:

我有一个 Laravel 应用程序,它有一个常见的操作,用户从一个名为 Sign 的数据库表中获取大约 300,000 行,其中包含 3 列。表列说明如下:id(int-10), sign(varchar-16), status (int-10)

该表有大约 3 亿个条目。当用户输入一些条目时,这些行的状态列将更改为用户的 id。请注意,用户每次总是输入大约 300,000 个条目。

我已将 innodb_buffer_pool_size 和 innodb_log_file_size 分别增加到 2GB 和 1GB。系统有 3.75GB 的 RAM。

这是代码-

$collection = Sign::select('sign')
                    ->where('status', 0)
                    ->where(DB::raw('CHAR_LENGTH(sign)'), '=', 7)
                    ->take(300000);

//write the the signs in $collection in a file here

$collection->update(['status' => $user->id]);

在我的例子中,表格数据很容易在不到 1 秒的时间内获取。更新语句以前需要大约 100-200 秒,但最近我已将我的操作系统从 Ubuntu 14 升级到 16,在此更新语句之后大约需要 500-600 秒。 有没有办法让这个过程更快?我应该增加内存吗?

【问题讨论】:

  • 请注意,使用take 而不使用orderBy 没有多大意义,因为不清楚要获取哪些 300K 记录。对于关于优化更新的问题,您可能需要添加一些索引才能实现。
  • 这个计算也会影响你的表现->where(DB::raw('CHAR_LENGTH(sign)'), '=', 7),因为它不是真正值得索引的。即使status 已编入索引,索引也只能帮助您处理不同的数据。越是不同的索引越好。使用此 WHERE 子句的 SELECT(带有SQL_NO_CACHE)需要多长时间,
  • @TimBiegeleisen 在这种情况下获取哪个 300K 并不重要,这就是为什么不使用 orderBy。 char_length=7 和 status=0 的任何 300K 都可以使用。
  • @ArtisticPhoenix 你能告诉我你所说的 SQL_NO_CACHE 是什么意思吗?选择查询不需要很长时间,不到一秒。
  • 您只需像这样添加它SELECT SQL_NO_CACHE id, foo FROM 它只是告诉数据库不要从缓存中获取结果。更好地了解 SELECT 的速度。见stackoverflow.com/questions/18777975/when-to-use-sql-no-cache

标签: php mysql sql ubuntu optimization


【解决方案1】:

对于 3.75GB 的 RAM,buffer_pool 的 2G 高得危险。系统换了吗?如果是这样,请降低 buffer_pool 或增加 RAM。交换对于 MySQL 来说很糟糕。

由于新的操作系统可能为自己占用更多的 RAM,上述陈述可能解释了减速的原因。

请提供$collection->update(['status' => $user->id]);生成的SQL; 很明显它会是什么。据我所知,$collection 保留了所有行的 300K id 的列表,并为UPDATE 创建了一个IN 子句。

UPDATEing rows 比SELECTing 他们的成本高得多。前者必须保留行的副本,以防发生需要ROLLBACK 的崩溃。

什么版本的 MySQL? UPDATE 的优化器最近发生了变化。

如果SELECTUPDATE 之间有东西,您可能需要SELECT ... FOR UPDATE——否则另一个连接可能会抓取相同的行,并弄乱数据!

【讨论】:

  • 感谢您的意见。使用这 2GB 的 RAM 作为缓冲池,系统运行良好。此外,延迟已自动修复,我猜这是由于缓存。由于系统更新并重新启动,我认为缓存搞砸了。
  • @AshfaqAdib - “状态”的 4 个字节?使用TINYINT 可以节省 3 个字节/行。
  • 这里的状态是另一个表的id,所以我必须有32位。
  • @AshfaqAdib - 如果那是“标准化”,status 有多少个不同的值?如果只有几个,那么再次考虑两个表的TINYINT
  • 有很多,UINT 是正确的数据类型。
【解决方案2】:

为了克服CHAR_LENGTH(sign) 不使用索引的缓慢问题,generated columns 提供了解决方案。

这里我们创建一个sign_length,计算为一列的符号长度:

ALTER TABLE sign ADD sign_length INT UNSIGNED AS (CHAR_LENGTH(sign))
, ADD INDEX status_sign_length(status,sign_length)

然后使用:

$collection = Sign::where('status', 0)
                ->where('sign_length', 7)
                ->update(['status' => $user->id])
                ->take(300000);

注意:larvel不是我的强项,欢迎指正。

【讨论】:

    【解决方案3】:

    您只需像在 laravel eloquent 中一样直接点击更新查询,而不是选择和更新,

    $collection = Sign::where('status', 0)
                    ->whereRaw('CHAR_LENGTH(sign) = 7')
                    ->update(['status' => $user->id]);
    

    在任何数据库中,选择操作总是很快,因为它只涉及扫描表,您可能会获得更多详细信息,例如EXPLAIN SELECT * FROM sign;

    【讨论】:

    • 但这不会让它更快吧?
    • 试试看。 1 次更新,意味着 1 次扫描而不是两次。
    • @AshfaqAdib 请尝试体验
    • @Chintan7027 对不起,我忘了说,在选择和更新查询之间有一些东西,所以我需要单独选择查询。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-01
    • 1970-01-01
    • 2016-08-22
    相关资源
    最近更新 更多