Make WordPress Core

Changeset 30238

11/05/2014 12:59:51 AM (10 years ago)

In wp_insert_term(), clean up accidental duplicate terms after insert.

In [30056], the UNIQUE index was removed from the 'slug' column of wp_terms.
While we have numerous checks in place to avoid the creation of unwanted
duplicate term+term_taxonomy pairs, it's possible that in certain edge cases -
such as during the lag caused by database replication, or a race condition
involving near-simultaneous creation of more than one term - we'll end up
unwittingly inserting two identical rows.

The current changeset minimizes this risk by introducing a failsafe mechanism
into wp_insert_term(). After a term and term_taxonomy are INSERTed, we check
to see whether the term just created is a duplicate of an existing term; if so,
we delete the new one and keep the old one. This prevents problems caused by
replication lag, because SELECT queries that take place after an INSERT will
hit the master database; it mitigates race conditions by enforcing that the
term that was created first (ie, the one with the lowest term_id) is
always the "canonical" one.

Props nacin, markjaquith.
See #22023, #5809.

1 edited


  • trunk/src/wp-includes/taxonomy.php

    r30210 r30238  
    29032903    $tt_id = (int) $wpdb->insert_id;
     2905    /*
     2906     * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
     2907     * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
     2908     * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
     2909     * are not fired.
     2910     */
     2911    $duplicate_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.term_id, tt.term_taxonomy_id FROM $wpdb->terms t INNER JOIN $wpdb->term_taxonomy tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id ) );
     2912    if ( $duplicate_term ) {
     2913        $wpdb->delete( $wpdb->terms, array( 'term_id' => $term_id ) );
     2914        $wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $tt_id ) );
     2916        $term_id = (int) $duplicate_term->term_id;
     2917        $tt_id   = (int) $duplicate_term->term_taxonomy_id;
     2919        clean_term_cache( $term_id, $taxonomy );
     2920        return array( 'term_id' => $term_id, 'term_taxonomy_id' => $tt_id );
     2921    }
    29052923    /**
    29062924     * Fires immediately after a new term is created, before the term cache is cleaned.
Note: See TracChangeset for help on using the changeset viewer.