Skip to content

Commit 9e4afd9

Browse files
committed
Chunk query that can potentially OOM
Unbound query has potential to return millions of rows, and it's fairly easy to run into memory problems. e.g. we had a problem with a ~4GB DB that was returning 13 million rows to check for the `meta_value` column. The fix loops through the table fetching 1000 at a time. Added ORDER BY statement, so we can safely iterate through the primary keys. Using ASC ordering, so the new items come last, in case something gets added while it's running.
1 parent e53777b commit 9e4afd9

File tree

1 file changed

+39
-30
lines changed

1 file changed

+39
-30
lines changed

src/Search_Replace_Command.php

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -533,46 +533,55 @@ private function php_handle_col( $col, $primary_keys, $table, $old, $new ) {
533533
$col_sql = self::esc_sql_ident( $col );
534534
$where = $this->regex ? '' : " WHERE $col_sql" . $wpdb->prepare( ' LIKE BINARY %s', '%' . self::esc_like( $old ) . '%' );
535535
$primary_keys_sql = implode( ',', self::esc_sql_ident( $primary_keys ) );
536+
$order_by_keys = array_map(
537+
function( $key ) {
538+
return "{$key} ASC";
539+
},
540+
$primary_keys
541+
);
542+
$order_by_sql = "ORDER BY " . implode( ',', self::esc_sql_ident( $order_by_keys ) );
543+
$limit = 1000;
544+
$offset = 0;
536545

537546
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident
538-
$rows = $wpdb->get_results( "SELECT {$primary_keys_sql} FROM {$table_sql} {$where}" );
539-
540-
foreach ( $rows as $keys ) {
541-
$where_sql = '';
542-
foreach ( (array) $keys as $k => $v ) {
543-
if ( strlen( $where_sql ) ) {
544-
$where_sql .= ' AND ';
547+
while ( $rows = $wpdb->get_results( "SELECT {$primary_keys_sql} FROM {$table_sql} {$where} {$order_by_sql} LIMIT {$limit} OFFSET {$offset}" ) ) {
548+
foreach ( $rows as $keys ) {
549+
$where_sql = '';
550+
foreach ( (array) $keys as $k => $v ) {
551+
if ( strlen( $where_sql ) ) {
552+
$where_sql .= ' AND ';
553+
}
554+
$where_sql .= self::esc_sql_ident( $k ) . ' = ' . self::esc_sql_value( $v );
545555
}
546-
$where_sql .= self::esc_sql_ident( $k ) . ' = ' . self::esc_sql_value( $v );
547-
}
548556

549-
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident
550-
$col_value = $wpdb->get_var( "SELECT {$col_sql} FROM {$table_sql} WHERE {$where_sql}" );
551-
552-
if ( '' === $col_value ) {
553-
continue;
554-
}
557+
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident
558+
$col_value = $wpdb->get_var( "SELECT {$col_sql} FROM {$table_sql} WHERE {$where_sql}" );
555559

556-
$value = $replacer->run( $col_value );
560+
if ( '' === $col_value ) {
561+
continue;
562+
}
557563

558-
if ( $value === $col_value ) {
559-
continue;
560-
}
564+
$value = $replacer->run( $col_value );
561565

562-
if ( $this->log_handle ) {
563-
$this->log_php_diff( $col, $keys, $table, $old, $new, $replacer->get_log_data() );
564-
$replacer->clear_log_data();
565-
}
566+
if ( $value === $col_value ) {
567+
continue;
568+
}
566569

567-
if ( $this->dry_run ) {
568-
$count++;
569-
} else {
570-
$where = array();
571-
foreach ( (array) $keys as $k => $v ) {
572-
$where[ $k ] = $v;
570+
if ( $this->log_handle ) {
571+
$this->log_php_diff( $col, $keys, $table, $old, $new, $replacer->get_log_data() );
572+
$replacer->clear_log_data();
573573
}
574574

575-
$count += $wpdb->update( $table, array( $col => $value ), $where );
575+
if ( $this->dry_run ) {
576+
$count++;
577+
} else {
578+
$where = array();
579+
foreach ( (array) $keys as $k => $v ) {
580+
$where[ $k ] = $v;
581+
}
582+
583+
$count += $wpdb->update( $table, array( $col => $value ), $where );
584+
}
576585
}
577586
}
578587

0 commit comments

Comments
 (0)