Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 53 |
|
0.00% |
0 / 3 |
CRAP | |
0.00% |
0 / 1 |
RunBatchedQuery | |
0.00% |
0 / 53 |
|
0.00% |
0 / 3 |
56 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 42 |
|
0.00% |
0 / 1 |
30 | |||
getDbType | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Run a database query in batches and wait for replica DBs. This is used on large |
4 | * wikis to prevent replication lag from going through the roof when executing |
5 | * large write queries. |
6 | * |
7 | * This program is free software; you can redistribute it and/or modify |
8 | * it under the terms of the GNU General Public License as published by |
9 | * the Free Software Foundation; either version 2 of the License, or |
10 | * (at your option) any later version. |
11 | * |
12 | * This program is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | * GNU General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU General Public License along |
18 | * with this program; if not, write to the Free Software Foundation, Inc., |
19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
20 | * http://www.gnu.org/copyleft/gpl.html |
21 | * |
22 | * @file |
23 | * @ingroup Maintenance |
24 | */ |
25 | |
26 | // @codeCoverageIgnoreStart |
27 | require_once __DIR__ . '/Maintenance.php'; |
28 | // @codeCoverageIgnoreEnd |
29 | |
30 | use Wikimedia\Rdbms\Platform\ISQLPlatform; |
31 | |
32 | /** |
33 | * Maintenance script to run a database query in batches and wait for replica DBs. |
34 | * |
35 | * @ingroup Maintenance |
36 | */ |
37 | class RunBatchedQuery extends Maintenance { |
38 | public function __construct() { |
39 | parent::__construct(); |
40 | $this->addDescription( |
41 | "Run an update query on all rows of a table. " . |
42 | "Waits for replicas at appropriate intervals." ); |
43 | $this->addOption( 'table', 'The table name', true, true ); |
44 | $this->addOption( 'set', 'The SET clause', true, true ); |
45 | $this->addOption( 'where', 'The WHERE clause', false, true ); |
46 | $this->addOption( 'key', 'A column name, the values of which are unique', true, true ); |
47 | $this->addOption( 'batch-size', 'The batch size (default 1000)', false, true ); |
48 | $this->addOption( 'db', 'The database name, or omit to use the current wiki.', false, true ); |
49 | } |
50 | |
51 | public function execute() { |
52 | $table = $this->getOption( 'table' ); |
53 | $key = $this->getOption( 'key' ); |
54 | $set = $this->getOption( 'set' ); |
55 | $where = $this->getOption( 'where', null ); |
56 | $where = $where === null ? [] : [ $where ]; |
57 | $batchSize = $this->getOption( 'batch-size', 1000 ); |
58 | |
59 | $dbName = $this->getOption( 'db', null ); |
60 | if ( $dbName === null ) { |
61 | $dbw = $this->getPrimaryDB(); |
62 | } else { |
63 | $dbw = $this->getServiceContainer()->getConnectionProvider()->getPrimaryDatabase( $dbName ); |
64 | } |
65 | |
66 | $selectConds = $where; |
67 | $prevEnd = false; |
68 | |
69 | $n = 1; |
70 | do { |
71 | $this->output( "Batch $n: " ); |
72 | $n++; |
73 | |
74 | // Note that the update conditions do not rely on the atomicity of the |
75 | // SELECT query in order to guarantee that all rows are updated. The |
76 | // results of the SELECT are merely a partitioning hint. Simultaneous |
77 | // updates merely result in the wrong number of rows being updated |
78 | // in a batch. |
79 | |
80 | $res = $dbw->newSelectQueryBuilder() |
81 | ->select( $key ) |
82 | ->from( $table ) |
83 | ->where( $selectConds ) |
84 | ->orderBy( $key ) |
85 | ->limit( $batchSize ) |
86 | ->caller( __METHOD__ ) |
87 | ->fetchResultSet(); |
88 | |
89 | if ( $res->numRows() ) { |
90 | $res->seek( $res->numRows() - 1 ); |
91 | $row = $res->fetchObject(); |
92 | $end = $row->$key; |
93 | $selectConds = array_merge( $where, [ $dbw->expr( $key, '>', $end ) ] ); |
94 | $updateConds = array_merge( $where, [ $dbw->expr( $key, '<=', $end ) ] ); |
95 | } else { |
96 | $updateConds = $where; |
97 | $end = false; |
98 | } |
99 | if ( $prevEnd !== false ) { |
100 | $updateConds = array_merge( [ $dbw->expr( $key, '>', $prevEnd ) ], $updateConds ); |
101 | } |
102 | |
103 | $query = "UPDATE " . $dbw->tableName( $table ) . |
104 | " SET " . $set . |
105 | " WHERE " . $dbw->makeList( $updateConds, ISQLPlatform::LIST_AND ); |
106 | |
107 | $dbw->query( $query, __METHOD__ ); |
108 | |
109 | $prevEnd = $end; |
110 | |
111 | $affected = $dbw->affectedRows(); |
112 | $this->output( "$affected rows affected\n" ); |
113 | $this->waitForReplication(); |
114 | } while ( $res->numRows() ); |
115 | } |
116 | |
117 | public function getDbType() { |
118 | return Maintenance::DB_ADMIN; |
119 | } |
120 | } |
121 | |
122 | // @codeCoverageIgnoreStart |
123 | $maintClass = RunBatchedQuery::class; |
124 | require_once RUN_MAINTENANCE_IF_MAIN; |
125 | // @codeCoverageIgnoreEnd |