h2_update_perf2.patch
new/src/main/org/h2/command/dml/Update.java 2022-11-28 15:03:50 +0000 | ||
---|---|---|
5 | 5 |
*/ |
6 | 6 |
package org.h2.command.dml; |
7 | 7 | |
8 |
import java.util.ArrayList; |
|
9 |
import java.util.BitSet; |
|
8 | 10 |
import java.util.HashSet; |
9 | 11 |
import java.util.LinkedHashMap; |
12 |
import java.util.Map; |
|
10 | 13 |
import java.util.Map.Entry; |
11 | 14 |
import java.util.Objects; |
12 | 15 | |
... | ... | |
21 | 24 |
import org.h2.expression.ExpressionVisitor; |
22 | 25 |
import org.h2.expression.Parameter; |
23 | 26 |
import org.h2.expression.ValueExpression; |
27 |
import org.h2.index.Index; |
|
24 | 28 |
import org.h2.message.DbException; |
25 | 29 |
import org.h2.result.ResultInterface; |
26 | 30 |
import org.h2.result.ResultTarget; |
... | ... | |
116 | 120 |
public int update() { |
117 | 121 |
targetTableFilter.startQuery(session); |
118 | 122 |
targetTableFilter.reset(); |
119 |
try (RowList rows = new RowList(session)) { |
|
123 |
try (RowList rows = new RowList(session); |
|
124 |
RowList inMemRows = new RowList(session)) { |
|
120 | 125 |
Table table = targetTableFilter.getTable(); |
126 |
|
|
127 |
// this is enabled only for PageStore and in-mem databases; in PageStore, the first index in the |
|
128 |
// list is the ScanIndex, which is not included in this optimization. |
|
129 |
boolean inMemUpdate = !table.getDatabase().isPersistent() && !table.getDatabase().isMVStore(); |
|
130 |
|
|
121 | 131 |
session.getUser().checkRight(table, Right.UPDATE); |
122 | 132 |
table.fire(session, Trigger.UPDATE, true); |
123 | 133 |
table.lock(session, true, false); |
... | ... | |
133 | 143 |
limitRows = v.getInt(); |
134 | 144 |
} |
135 | 145 |
} |
146 | ||
147 |
BitSet dirtyCols = inMemUpdate ? new BitSet(columnCount) : null; |
|
148 |
|
|
136 | 149 |
while (targetTableFilter.next()) { |
137 | 150 |
setCurrentRowNumber(count+1); |
138 | 151 |
if (limitRows >= 0 && count >= limitRows) { |
... | ... | |
154 | 167 |
} |
155 | 168 |
} |
156 | 169 |
Row newRow = table.getTemplateRow(); |
157 |
boolean setOnUpdate = false; |
|
158 |
for (int i = 0; i < columnCount; i++) { |
|
159 |
Column column = columns[i]; |
|
160 |
Expression newExpr = setClauseMap.get(column); |
|
161 |
Value newValue; |
|
162 |
if (newExpr == null) { |
|
163 |
if (column.getOnUpdateExpression() != null) { |
|
164 |
setOnUpdate = true; |
|
165 |
} |
|
166 |
newValue = oldRow.getValue(i); |
|
167 |
} else if (newExpr == ValueExpression.getDefault()) { |
|
168 |
newValue = table.getDefaultValue(session, column); |
|
169 |
} else { |
|
170 |
newValue = newExpr.getValue(session); |
|
171 |
} |
|
172 |
newRow.setValue(i, newValue); |
|
173 |
} |
|
170 |
newRow.copy(oldRow); |
|
171 |
|
|
172 |
Row oldRowCopy = null; |
|
173 |
if (inMemUpdate) |
|
174 |
{ |
|
175 |
// create a copy for the triggers |
|
176 |
oldRowCopy = table.getTemplateRow(); |
|
177 |
oldRowCopy.copy(oldRow); |
|
178 |
oldRowCopy.setKey(oldRow.getKey()); |
|
179 |
} |
|
180 |
|
|
181 |
BitSet onUpdateColumns = null; |
|
182 |
if (table.getOnUpdateColumns() != null && !table.getOnUpdateColumns().isEmpty()) |
|
183 |
{ |
|
184 |
onUpdateColumns = new BitSet(columnCount); |
|
185 |
ArrayList<Integer> onUpdate = table.getOnUpdateColumns(); |
|
186 |
for (int i = 0; i < onUpdate.size(); i++) |
|
187 |
{ |
|
188 |
onUpdateColumns.set(onUpdate.get(i)); |
|
189 |
} |
|
190 |
} |
|
191 |
BitSet changed = new BitSet(columnCount); |
|
192 |
|
|
193 |
for (Map.Entry<Column, Expression> entry : setClauseMap.entrySet()) |
|
194 |
{ |
|
195 |
Column column = entry.getKey(); |
|
196 |
Expression newExpr = entry.getValue(); |
|
197 |
Value newValue; |
|
198 |
if (newExpr == ValueExpression.getDefault()) |
|
199 |
{ |
|
200 |
newValue = table.getDefaultValue(session, column); |
|
201 |
} |
|
202 |
else |
|
203 |
{ |
|
204 |
newValue = newExpr.getValue(session); |
|
205 |
} |
|
206 |
newRow.setValue(column.getColumnId(), newValue); |
|
207 |
changed.set(column.getColumnId()); |
|
208 |
if (onUpdateColumns != null) |
|
209 |
{ |
|
210 |
onUpdateColumns.clear(column.getColumnId()); |
|
211 |
} |
|
212 |
} |
|
213 |
boolean setOnUpdate = onUpdateColumns != null && !onUpdateColumns.isEmpty(); |
|
174 | 214 |
long key = oldRow.getKey(); |
175 | 215 |
newRow.setKey(key); |
176 |
table.validateConvertUpdateSequence(session, newRow); |
|
216 |
table.validateConvertUpdateSequence(session, newRow, changed);
|
|
177 | 217 |
if (setOnUpdate || updateToCurrentValuesReturnsZero) { |
178 | 218 |
setOnUpdate = false; |
179 |
for (int i = 0; i < columnCount; i++) {
|
|
219 |
for (int i = changed.nextSetBit(0); i >= 0; i = changed.nextSetBit(i + 1)) {
|
|
180 | 220 |
// Use equals here to detect changes from numeric 0 to 0.0 and similar |
181 | 221 |
if (!Objects.equals(oldRow.getValue(i), newRow.getValue(i))) { |
182 | 222 |
setOnUpdate = true; |
... | ... | |
184 | 224 |
} |
185 | 225 |
} |
186 | 226 |
if (setOnUpdate) { |
187 |
for (int i = 0; i < columnCount; i++) { |
|
188 |
Column column = columns[i]; |
|
189 |
if (setClauseMap.get(column) == null) { |
|
190 |
if (column.getOnUpdateExpression() != null) { |
|
191 |
newRow.setValue(i, table.getOnUpdateValue(session, column)); |
|
192 |
} |
|
193 |
} |
|
227 |
if (onUpdateColumns != null) |
|
228 |
for (int i = onUpdateColumns.nextSetBit(0); i >= 0; i = onUpdateColumns.nextSetBit(i + 1)) { |
|
229 |
Column column = columns[i]; |
|
230 |
newRow.setValue(i, table.getOnUpdateValue(session, column)); |
|
231 |
changed.set(i); |
|
194 | 232 |
} |
195 | 233 |
} else if (updateToCurrentValuesReturnsZero) { |
196 | 234 |
count--; |
... | ... | |
202 | 240 |
deltaChangeCollector.addRow(newRow.getValueList().clone()); |
203 | 241 |
} |
204 | 242 |
if (!table.fireRow() || !table.fireBeforeRow(session, oldRow, newRow)) { |
205 |
rows.add(oldRow); |
|
206 |
rows.add(newRow); |
|
243 |
|
|
244 |
if (inMemUpdate) |
|
245 |
{ |
|
246 |
dirtyCols.or(changed); |
|
247 |
|
|
248 |
inMemRows.add(oldRow); |
|
249 |
inMemRows.add(newRow); |
|
250 |
|
|
251 |
rows.add(oldRowCopy); |
|
252 |
rows.add(newRow); |
|
253 |
} |
|
254 |
else |
|
255 |
{ |
|
256 |
rows.add(oldRow); |
|
257 |
rows.add(newRow); |
|
258 |
} |
|
259 |
|
|
207 | 260 |
if (updatedKeysCollector != null) { |
208 | 261 |
updatedKeysCollector.add(key); |
209 | 262 |
} |
... | ... | |
221 | 274 |
// TODO update in-place (but if the key changes, |
222 | 275 |
// we need to update all indexes) before row triggers |
223 | 276 | |
224 |
// the cached row is already updated - we need the old values |
|
225 |
table.updateRows(this, session, rows); |
|
277 |
boolean updateRows = !inMemUpdate; |
|
278 |
|
|
279 |
if (!updateRows) |
|
280 |
{ |
|
281 |
ArrayList<Index> indexes = table.getIndexes(); |
|
282 |
// skip scan index |
|
283 |
for (int i = 1; !updateRows && i < indexes.size(); i++) |
|
284 |
{ |
|
285 |
Index index = indexes.get(i); |
|
286 |
Column[] idxColumns = index.getColumns(); |
|
287 |
for (int j = 0; j < idxColumns.length; j++) |
|
288 |
{ |
|
289 |
if (dirtyCols.get(idxColumns[j].getColumnId())) |
|
290 |
{ |
|
291 |
// if there is any touched index in this update, then use the 'updateRows' approach |
|
292 |
// this is required because for in-memory database, a single row instance is kept |
|
293 |
// everywhere, and the index update can be done only by removing and adding again the |
|
294 |
// row |
|
295 |
|
|
296 |
updateRows = true; |
|
297 |
break; |
|
298 |
} |
|
299 |
} |
|
300 |
} |
|
301 |
} |
|
302 |
|
|
303 |
if (updateRows) |
|
304 |
{ |
|
305 |
table.updateRows(this, session, rows); |
|
306 |
} |
|
307 |
else |
|
308 |
{ |
|
309 |
for (inMemRows.reset(); inMemRows.hasNext();) { |
|
310 |
// copy the old row to new, as no index is affected |
|
311 |
Row o = inMemRows.next(); |
|
312 |
Row n = inMemRows.next(); |
|
313 |
o.copy(n); |
|
314 |
} |
|
315 |
} |
|
316 | ||
226 | 317 |
if (table.fireRow()) { |
227 | 318 |
for (rows.reset(); rows.hasNext();) { |
228 | 319 |
Row o = rows.next(); |
new/src/main/org/h2/pagestore/db/TreeIndex.java 2022-11-28 10:36:42 +0000 | ||
---|---|---|
5 | 5 |
*/ |
6 | 6 |
package org.h2.pagestore.db; |
7 | 7 | |
8 |
import java.util.HashMap; |
|
9 | ||
8 | 10 |
import org.h2.command.dml.AllColumnsForPlan; |
9 | 11 |
import org.h2.engine.Session; |
10 | 12 |
import org.h2.index.BaseIndex; |
... | ... | |
25 | 27 |
public class TreeIndex extends BaseIndex { |
26 | 28 | |
27 | 29 |
private TreeNode root; |
30 |
private HashMap<Long, TreeNode> byKey = new HashMap<>(); |
|
28 | 31 |
private final PageStoreTable tableData; |
29 | 32 |
private long rowCount; |
30 | 33 |
private boolean closed; |
... | ... | |
41 | 44 |
@Override |
42 | 45 |
public void close(Session session) { |
43 | 46 |
root = null; |
47 |
byKey.clear(); |
|
44 | 48 |
closed = true; |
45 | 49 |
} |
46 | 50 | |
... | ... | |
50 | 54 |
throw DbException.throwInternalError(); |
51 | 55 |
} |
52 | 56 |
TreeNode i = new TreeNode(row); |
57 |
byKey.put(row.getKey(), i); |
|
53 | 58 |
TreeNode n = root, x = n; |
54 | 59 |
boolean isLeft = true; |
55 | 60 |
while (true) { |
... | ... | |
157 | 162 |
if (x == null) { |
158 | 163 |
throw DbException.throwInternalError("not found!"); |
159 | 164 |
} |
165 |
byKey.remove(row.getKey()); |
|
166 |
|
|
160 | 167 |
TreeNode n; |
161 | 168 |
if (x.left == null) { |
162 | 169 |
n = x.right; |
... | ... | |
272 | 279 |
} |
273 | 280 | |
274 | 281 |
private TreeNode findFirstNode(SearchRow row, boolean withKey) { |
282 |
TreeNode k = null; |
|
283 |
if (withKey) |
|
284 |
{ |
|
285 |
k = byKey.get(row.getKey()); |
|
286 |
return k; |
|
287 |
} |
|
288 |
|
|
275 | 289 |
TreeNode x = root, result = x; |
276 | 290 |
while (x != null) { |
277 | 291 |
result = x; |
... | ... | |
334 | 348 |
@Override |
335 | 349 |
public void truncate(Session session) { |
336 | 350 |
root = null; |
351 |
byKey.clear(); |
|
337 | 352 |
rowCount = 0; |
338 | 353 |
} |
339 | 354 |
new/src/main/org/h2/result/Row.java 2022-11-28 10:36:42 +0000 | ||
---|---|---|
66 | 66 |
*/ |
67 | 67 |
boolean hasSharedData(Row other); |
68 | 68 | |
69 |
void copy(Row oldRow); |
|
70 | ||
69 | 71 |
} |
new/src/main/org/h2/result/RowImpl.java 2022-11-28 10:36:42 +0000 | ||
---|---|---|
25 | 25 |
} |
26 | 26 | |
27 | 27 |
@Override |
28 |
public void copy(Row src) |
|
29 |
{ |
|
30 |
System.arraycopy(((RowImpl) src).data, 0, data, 0, data.length); |
|
31 |
} |
|
32 |
|
|
33 |
@Override |
|
28 | 34 |
public void setKey(SearchRow row) { |
29 | 35 |
setKey(row.getKey()); |
30 | 36 |
} |
new/src/main/org/h2/table/Table.java 2022-11-28 15:04:15 +0000 | ||
---|---|---|
62 | 62 |
/** |
63 | 63 |
* The indexes of the computed columns |
64 | 64 |
*/ |
65 |
protected List<Integer> computedColumns = new ArrayList<>(); |
|
65 |
protected List<Integer> computedColumns = null; |
|
66 |
|
|
67 |
private ArrayList<Integer> onUpdateColumns = null; |
|
66 | 68 | |
67 | 69 |
/** |
68 | 70 |
* The compare mode used for this table. |
... | ... | |
271 | 273 |
*/ |
272 | 274 |
public abstract ArrayList<Index> getIndexes(); |
273 | 275 | |
276 |
public ArrayList<Index> getIndexes(BitSet columns) |
|
277 |
{ |
|
278 |
ArrayList<Index> result = new ArrayList<>(); |
|
279 |
|
|
280 |
ArrayList<Index> indexes = getIndexes(); |
|
281 |
for (int i = 0; i < indexes.size(); i++) |
|
282 |
{ |
|
283 |
Index index = indexes.get(i); |
|
284 |
Column[] cols = index.getColumns(); |
|
285 |
for (int j = 0; j < cols.length; j++) |
|
286 |
{ |
|
287 |
if (columns.get(cols[j].getColumnId())) |
|
288 |
{ |
|
289 |
result.add(index); |
|
290 |
break; |
|
291 |
} |
|
292 |
} |
|
293 |
} |
|
294 |
|
|
295 |
return result; |
|
296 |
} |
|
297 | ||
298 |
public ArrayList<Integer> getOnUpdateColumns() |
|
299 |
{ |
|
300 |
return onUpdateColumns; |
|
301 |
} |
|
302 |
|
|
274 | 303 |
/** |
275 | 304 |
* Get an index by name. |
276 | 305 |
* |
... | ... | |
435 | 464 |
if (columnMap.size() > 0) { |
436 | 465 |
columnMap.clear(); |
437 | 466 |
} |
438 |
if (computedColumns.size() > 0) { |
|
467 |
if (computedColumns != null && computedColumns.size() > 0) {
|
|
439 | 468 |
computedColumns.clear(); |
440 | 469 |
} |
470 |
if (onUpdateColumns != null && onUpdateColumns.size() > 0) |
|
471 |
{ |
|
472 |
onUpdateColumns.clear(); |
|
473 |
} |
|
441 | 474 |
|
442 | 475 |
for (int i = 0; i < columns.length; i++) { |
443 | 476 |
Column col = columns[i]; |
... | ... | |
453 | 486 |
} |
454 | 487 |
columnMap.put(columnName, col); |
455 | 488 |
if (col.getComputed()) { |
489 |
if (computedColumns == null) |
|
490 |
{ |
|
491 |
computedColumns = new ArrayList<>(); |
|
492 |
} |
|
456 | 493 |
computedColumns.add(i); |
457 | 494 |
} |
495 |
if (col.getOnUpdateExpression() != null) |
|
496 |
{ |
|
497 |
if (onUpdateColumns == null) |
|
498 |
{ |
|
499 |
onUpdateColumns = new ArrayList<>(); |
|
500 |
} |
|
501 |
onUpdateColumns.add(i); |
|
502 |
} |
|
458 | 503 |
} |
459 | 504 |
} |
460 | 505 | |
... | ... | |
841 | 886 |
* @param row the row |
842 | 887 |
*/ |
843 | 888 |
public void validateConvertUpdateSequence(Session session, Row row) { |
844 |
for (int i = 0; i < computedColumns.size(); i++) |
|
845 |
{ |
|
846 |
int idx = computedColumns.get(i); |
|
847 |
row.setValue(idx, columns[idx].computeValue(session, row)); |
|
848 |
} |
|
849 |
|
|
889 |
validateConvertUpdateSequence(session, row, null); |
|
890 |
} |
|
891 |
|
|
892 |
/** |
|
893 |
* Validate all values in this row, convert the values if required, and |
|
894 |
* update the sequence values if required. This call will also set the |
|
895 |
* default values if required and set the computed column if there are any. |
|
896 |
* |
|
897 |
* @param session the session |
|
898 |
* @param row the row |
|
899 |
*/ |
|
900 |
public void validateConvertUpdateSequence(Session session, Row row, BitSet setCols) { |
|
901 |
BitSet changed = null; |
|
902 |
if (setCols != null) |
|
903 |
{ |
|
904 |
changed = new BitSet(); |
|
905 |
changed.or(setCols); |
|
906 |
} |
|
907 |
|
|
908 |
if (computedColumns != null) |
|
909 |
{ |
|
910 |
for (int i = 0; i < computedColumns.size(); i++) |
|
911 |
{ |
|
912 |
int idx = computedColumns.get(i); |
|
913 |
row.setValue(idx, columns[idx].computeValue(session, row)); |
|
914 |
if (changed != null) |
|
915 |
{ |
|
916 |
changed.set(idx); |
|
917 |
} |
|
918 |
} |
|
919 |
} |
|
920 |
|
|
850 | 921 |
for (int i = 0; i < columns.length; i++) { |
922 |
if (changed != null && !changed.get(i)) |
|
923 |
{ |
|
924 |
// only changed columns |
|
925 |
continue; |
|
926 |
} |
|
927 |
|
|
851 | 928 |
Value value = row.getValue(i); |
852 | 929 |
Value v2 = columns[i].validateConvertUpdateSequence(session, value); |
853 | 930 |
if (v2 != value) { |