const LOAD_ONLY = 0;
const LAZY_INIT_ROW = 1;
+ const ROW_COUNT_SMALL = 100;
+
private function __construct() {
}
* @since 1.32
*/
public function refreshCountsIfEmpty() {
+ return $this->refreshCountsIfSmall( 0 );
+ }
+
+ /**
+ * Call refreshCounts() if there are few entries in the categorylinks table
+ *
+ * Due to lock errors or other failures, the precomputed counts can get out of sync,
+ * making it hard to know when to delete the category row without checking the
+ * categorylinks table.
+ *
+ * This method will do a non-locking select first to reduce contention.
+ *
+ * @param int $maxSize Only refresh if there are this or less many backlinks
+ * @return bool Whether links were refreshed
+ * @since 1.34
+ */
+ public function refreshCountsIfSmall( $maxSize = self::ROW_COUNT_SMALL ) {
$dbw = wfGetDB( DB_MASTER );
+ $dbw->startAtomic( __METHOD__ );
- $hasLink = $dbw->selectField(
+ $typeOccurances = $dbw->selectFieldValues(
'categorylinks',
- '1',
+ 'cl_type',
[ 'cl_to' => $this->getName() ],
- __METHOD__
+ __METHOD__,
+ [ 'LIMIT' => $maxSize + 1 ]
);
- if ( !$hasLink ) {
- $this->refreshCounts(); // delete any category table entry
- return true;
+ if ( !$typeOccurances ) {
+ $doRefresh = true; // delete any category table entry
+ } elseif ( count( $typeOccurances ) <= $maxSize ) {
+ $countByType = array_count_values( $typeOccurances );
+ $doRefresh = !$dbw->selectField(
+ 'category',
+ '1',
+ [
+ 'cat_title' => $this->getName(),
+ 'cat_pages' => $countByType['page'] ?? 0,
+ 'cat_subcats' => $countByType['subcat'] ?? 0,
+ 'cat_files' => $countByType['file'] ?? 0
+ ],
+ __METHOD__
+ );
+ } else {
+ $doRefresh = false; // category is too big
}
- $hasBadRow = $dbw->selectField(
- 'category',
- '1',
- [ 'cat_title' => $this->getName(), 'cat_pages <= 0' ],
- __METHOD__
- );
- if ( $hasBadRow ) {
- $this->refreshCounts(); // clean up this row
+ $dbw->endAtomic( __METHOD__ );
+
+ if ( $doRefresh ) {
+ $this->refreshCounts(); // update the row
return true;
}