【问题标题】:SQL Update taking a long timeSQL 更新需要很长时间
【发布时间】:2021-02-24 16:48:07
【问题描述】:

我正在尝试更新我的数据库中的许多行(100 000+),但这需要一段时间(超过 10 分钟但仍未完成)。我想知道这是预期的行为还是我的代码有问题。为了防止数据库在执行更新时挂起,我被告知一次更新一行,不确定是否应该这样实现。

如果这些歌曲在我的播放列表中播放,我会将歌曲表中的图像更新为 null

private function updateBlogSongs ($blog_id) {

        $db = Yii::app()->db;

        $affectedRows = 0;


        $sql = "SELECT *

                FROM `firstdatabase`.song s

                INNER JOIN `seconddatabase`.playlist p ON s.name LIKE p.song_name";


        $dataReader = $db->createCommand($sql)->query(); // Rows from the song table that were played in the given blog

        $row = $dataReader->read();


        while ($row != false) {

            $sql = "UPDATE `firstdatabase`.song s

                    SET s.image = NULL

                    WHERE s.song_id = " . $row['song_id'];


            $affectedRows += $db->createCommand($sql)->execute();

            

            $row = $dataReader->read();

        }


        return $affectedRows;

    }

编辑:在阅读了 The Dog 的评论后,我做了一些更改: 歌曲表中有 500 000 行,如果我将 batchSize 增加到 10000 大约需要 10 分钟(上面的代码需要 8 小时)。在批量大小为 250 时,大约需要 50 分钟。我选择 250 是因为查询运行大约需要 1 秒,并且以 10000 批量大小运行需要 10 多秒(约束是 1 秒)。我想让它更快,但不知道还有什么要改变的

$batchSize = 250;
        $lastSongID = 0;
        $rowIndex = 0;
        $affectedRows = 0;

        $sql = "SELECT max(song_id) FROM `firstdatabase`.song";
        $lastSongID = intval($db->createCommand($sql)->query()->read()['max(song_id)']);
        
        echo($lastSongID . ' songs in table.' . PHP_EOL);
        echo('Updating songs...' . PHP_EOL);

        while($rowIndex <= $lastSongID) {
            $startTime = microtime(true);

            
            $sql = "UPDATE `firstdatabase`.song
                    SET image = NULL
                    WHERE song_id in (
                        SELECT song_id
                        FROM (
                            SELECT song_id, name 
                            FROM `firstdatabase`.song 
                            WHERE song_id > " . $rowIndex . "
                            LIMIT " . $batchSize . "
                        ) s
                        INNER JOIN (
                            SELECT DISTINCT song_name 
                            FROM `seconddatabase`.playlist 
                        ) p ON s.name LIKE p.song_name
                        ORDER BY s.song_id ASC
                    )";

            $affectedRows += $db->createCommand($sql)->execute();


            $rowIndex += $batchSize;
            
            $endTime = microtime(true);
            $elapsedTime = round($endTime - $startTime, 2);
          
            
        }

【问题讨论】:

  • 可能是您可以避免循环并使用单个查询执行更新...尝试更新您的问题添加适当的数据样本和预期结果作为表格文本
  • 'LIKE' 仅在您使用区分大小写的排序规则时才有意义,并且锁定后的啤酒表明您不是。
  • 问题是您在 while 循环中运行 SQL。在 while 循环中执行数据库命令会使运行时间变成您所看到的大量时间。你必须把它从while循环中解脱出来。您可以在一次 DB 命中而不是在 while 循环中执行此操作。我将编辑我的回复。
  • 考虑您的 SQL,在您的第二个代码示例中,对于第一个数据库中的每首歌曲,您正在运行查询以更新也在第二个数据库播放列表中的 250 首歌曲。您不需要在 while 循环中执行此操作。

标签: php mysql sql yii


【解决方案1】:

这确实是 SQL 世界而不是 PHP 世界的问题,但这是我的建议:

不要在 while 循环中一次一行地执行此操作。制作一个更复杂的更新语句,可以在一次数据库命中中完成所有操作。数据库命令是 php 代码中最慢的部分,您希望限制对数据库的调用次数。

当您确信可以在一个 sql 命令中完成操作时,或者即使您认为不可能,也可以将代码拉入数据库中的存储过程中。将复杂的 sql 查询作为存储过程对维护代码有很大帮助。

确保您的表上有索引。您需要确保查询命中这些索引以获得最佳性能。

这里有一个单一查询的选项:

update `firstdatabase`.song
set image = null
where song_id in (
    select s.song_id 
    from `firstdatabase`.song s
    INNER JOIN `seconddatabase`.playlist p 
        ON s.name LIKE p.song_name"
);

显然我们无权访问您的数据库,因此您需要在必要时进行更改,但希望它能让您走上正轨。

编辑: 尝试用以下代码替换您的第二个代码集:

    $lastSongID = 0;
    $rowIndex = 0;
    $affectedRows = 0;
    
    $sql = "SELECT max(song_id) FROM `firstdatabase`.song";
    $lastSongID = intval($db->createCommand($sql)->query()->read()['max(song_id)']);
    
    echo($lastSongID . ' songs in table.' . PHP_EOL);
    echo('Updating songs...' . PHP_EOL);
    
    $startTime = microtime(true);
    $sql = "
        update `firstdatabase`.song
        set image = null
        where song_id in (
            select s.song_id 
            from `firstdatabase`.song s
            INNER JOIN `seconddatabase`.playlist p 
                ON s.name LIKE p.song_name"
        )";
    
    $affectedRows += $db->createCommand($sql)->execute();
        
    $endTime = microtime(true);
    $elapsedTime = round($endTime - $startTime, 2);

如果有效,请告诉我运行所需的时间,如果无效,是否是 SQL 的问题(我再次看不到表,所以我在猜测)。

【讨论】:

  • 感谢您的帮助,到目前为止,我在说明中进行了一些更改。我无法将它变成一个命令,这是一个查询,但我正在批量运行它。我也去看看有没有。我可以将代码拉入存储过程。它仍然需要一些时间,不知道还有什么可以改变来提高速度。表格也被索引
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-04-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多