h2_update_perf3.patch
new/src/main/org/h2/command/dml/Insert.java 2022-12-02 14:58:43 +0000 | ||
---|---|---|
184 | 184 |
} |
185 | 185 |
} |
186 | 186 |
rowNumber++; |
187 |
table.validateConvertUpdateSequence(session, newRow); |
|
187 |
table.validateConvertUpdateSequence(session, newRow, null);
|
|
188 | 188 |
if (deltaChangeCollectionMode == ResultOption.NEW) { |
189 | 189 |
deltaChangeCollector.addRow(newRow.getValueList().clone()); |
190 | 190 |
} |
... | ... | |
246 | 246 |
for (int j = 0, len = columns.length; j < len; j++) { |
247 | 247 |
newRow.setValue(columns[j].getColumnId(), values[j]); |
248 | 248 |
} |
249 |
table.validateConvertUpdateSequence(session, newRow); |
|
249 |
table.validateConvertUpdateSequence(session, newRow, null);
|
|
250 | 250 |
if (deltaChangeCollectionMode == ResultOption.NEW) { |
251 | 251 |
deltaChangeCollector.addRow(newRow.getValueList().clone()); |
252 | 252 |
} |
new/src/main/org/h2/command/dml/Merge.java 2022-12-02 14:58:33 +0000 | ||
---|---|---|
172 | 172 |
// if update fails try an insert |
173 | 173 |
if (count == 0) { |
174 | 174 |
try { |
175 |
table.validateConvertUpdateSequence(session, row); |
|
175 |
table.validateConvertUpdateSequence(session, row, null);
|
|
176 | 176 |
if (deltaChangeCollectionMode == ResultOption.NEW) { |
177 | 177 |
deltaChangeCollector.addRow(row.getValueList().clone()); |
178 | 178 |
} |
new/src/main/org/h2/command/dml/Update.java 2022-12-02 14:00:40 +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.useInPlaceUpdate(); |
|
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 |
if (column.getColumnId() >= 0) // there are negative-ID columns like '_rowid_' |
|
208 |
{ |
|
209 |
changed.set(column.getColumnId()); |
|
210 |
if (onUpdateColumns != null) |
|
211 |
{ |
|
212 |
onUpdateColumns.clear(column.getColumnId()); |
|
213 |
} |
|
214 |
} |
|
215 |
} |
|
216 |
boolean setOnUpdate = onUpdateColumns != null && !onUpdateColumns.isEmpty(); |
|
174 | 217 |
long key = oldRow.getKey(); |
175 | 218 |
newRow.setKey(key); |
176 |
table.validateConvertUpdateSequence(session, newRow); |
|
219 |
table.validateConvertUpdateSequence(session, newRow, changed);
|
|
177 | 220 |
if (setOnUpdate || updateToCurrentValuesReturnsZero) { |
178 | 221 |
setOnUpdate = false; |
179 |
for (int i = 0; i < columnCount; i++) {
|
|
222 |
for (int i = changed.nextSetBit(0); i >= 0; i = changed.nextSetBit(i + 1)) {
|
|
180 | 223 |
// Use equals here to detect changes from numeric 0 to 0.0 and similar |
181 | 224 |
if (!Objects.equals(oldRow.getValue(i), newRow.getValue(i))) { |
182 | 225 |
setOnUpdate = true; |
... | ... | |
184 | 227 |
} |
185 | 228 |
} |
186 | 229 |
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 |
} |
|
230 |
if (onUpdateColumns != null) |
|
231 |
for (int i = onUpdateColumns.nextSetBit(0); i >= 0; i = onUpdateColumns.nextSetBit(i + 1)) { |
|
232 |
Column column = columns[i]; |
|
233 |
newRow.setValue(i, table.getOnUpdateValue(session, column)); |
|
234 |
changed.set(i); |
|
194 | 235 |
} |
195 | 236 |
} else if (updateToCurrentValuesReturnsZero) { |
196 | 237 |
count--; |
... | ... | |
202 | 243 |
deltaChangeCollector.addRow(newRow.getValueList().clone()); |
203 | 244 |
} |
204 | 245 |
if (!table.fireRow() || !table.fireBeforeRow(session, oldRow, newRow)) { |
205 |
rows.add(oldRow); |
|
206 |
rows.add(newRow); |
|
246 |
|
|
247 |
if (inMemUpdate) |
|
248 |
{ |
|
249 |
dirtyCols.or(changed); |
|
250 |
|
|
251 |
inMemRows.add(oldRow); |
|
252 |
inMemRows.add(newRow); |
|
253 |
|
|
254 |
rows.add(oldRowCopy); |
|
255 |
rows.add(newRow); |
|
256 |
} |
|
257 |
else |
|
258 |
{ |
|
259 |
rows.add(oldRow); |
|
260 |
rows.add(newRow); |
|
261 |
} |
|
262 |
|
|
207 | 263 |
if (updatedKeysCollector != null) { |
208 | 264 |
updatedKeysCollector.add(key); |
209 | 265 |
} |
... | ... | |
221 | 277 |
// TODO update in-place (but if the key changes, |
222 | 278 |
// we need to update all indexes) before row triggers |
223 | 279 | |
224 |
// the cached row is already updated - we need the old values |
|
225 |
table.updateRows(this, session, rows); |
|
280 |
boolean updateRows = !inMemUpdate; |
|
281 |
|
|
282 |
if (!updateRows) |
|
283 |
{ |
|
284 |
ArrayList<Index> indexes = table.getIndexes(); |
|
285 |
// skip scan index |
|
286 |
for (int i = 1; !updateRows && i < indexes.size(); i++) |
|
287 |
{ |
|
288 |
Index index = indexes.get(i); |
|
289 |
Column[] idxColumns = index.getColumns(); |
|
290 |
for (int j = 0; j < idxColumns.length; j++) |
|
291 |
{ |
|
292 |
if (dirtyCols.get(idxColumns[j].getColumnId())) |
|
293 |
{ |
|
294 |
// if there is any touched index in this update, then use the 'updateRows' approach |
|
295 |
// this is required because for in-memory database, a single row instance is kept |
|
296 |
// everywhere, and the index update can be done only by removing and adding again the |
|
297 |
// row |
|
298 |
|
|
299 |
updateRows = true; |
|
300 |
break; |
|
301 |
} |
|
302 |
} |
|
303 |
} |
|
304 |
} |
|
305 |
|
|
306 |
if (updateRows) |
|
307 |
{ |
|
308 |
table.updateRows(this, session, rows); |
|
309 |
} |
|
310 |
else |
|
311 |
{ |
|
312 |
for (inMemRows.reset(); inMemRows.hasNext();) { |
|
313 |
// copy the old row to new, as no index is affected |
|
314 |
Row o = inMemRows.next(); |
|
315 |
Row n = inMemRows.next(); |
|
316 |
o.copy(n); |
|
317 |
} |
|
318 |
} |
|
319 | ||
226 | 320 |
if (table.fireRow()) { |
227 | 321 |
for (rows.reset(); rows.hasNext();) { |
228 | 322 |
Row o = rows.next(); |
new/src/main/org/h2/pagestore/db/PageStoreTable.java 2022-12-02 14:00:36 +0000 | ||
---|---|---|
66 | 66 |
indexes.add(scanIndex); |
67 | 67 |
traceLock = database.getTrace(Trace.LOCK); |
68 | 68 |
} |
69 |
|
|
70 |
@Override |
|
71 |
public boolean useInPlaceUpdate() |
|
72 |
{ |
|
73 |
return !database.isPersistent() && !database.isMVStore(); |
|
74 |
} |
|
69 | 75 | |
70 | 76 |
@Override |
71 | 77 |
public void close(Session session) { |
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-12-02 15:58:45 +0000 | ||
---|---|---|
66 | 66 |
*/ |
67 | 67 |
boolean hasSharedData(Row other); |
68 | 68 | |
69 |
|
|
70 |
/** |
|
71 |
* Perform a direct copy of the old row's data to this row. |
|
72 |
* |
|
73 |
* @param oldRow |
|
74 |
* The row to copy from. |
|
75 |
*/ |
|
76 |
void copy(Row oldRow); |
|
77 | ||
69 | 78 |
} |
new/src/main/org/h2/result/RowImpl.java 2022-12-02 15:59:02 +0000 | ||
---|---|---|
25 | 25 |
} |
26 | 26 | |
27 | 27 |
@Override |
28 |
public void copy(Row src) { |
|
29 |
System.arraycopy(((RowImpl) src).data, 0, data, 0, data.length); |
|
30 |
} |
|
31 |
|
|
32 |
@Override |
|
28 | 33 |
public void setKey(SearchRow row) { |
29 | 34 |
setKey(row.getKey()); |
30 | 35 |
} |
new/src/main/org/h2/table/Table.java 2022-12-02 16:02:02 +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 |
/** |
|
68 |
* The indexes of the columns with ON UPDATE clauses. |
|
69 |
*/ |
|
70 |
private ArrayList<Integer> onUpdateColumns = null; |
|
66 | 71 | |
67 | 72 |
/** |
68 | 73 |
* The compare mode used for this table. |
... | ... | |
272 | 277 |
public abstract ArrayList<Index> getIndexes(); |
273 | 278 | |
274 | 279 |
/** |
280 |
* Get all the indexes which use the specified columns. |
|
281 |
* |
|
282 |
* @param columns |
|
283 |
* The column IDs. |
|
284 |
* @return The list of indexes which use these columns. |
|
285 |
*/ |
|
286 |
public ArrayList<Index> getIndexes(BitSet columns) |
|
287 |
{ |
|
288 |
ArrayList<Index> result = new ArrayList<>(); |
|
289 |
|
|
290 |
ArrayList<Index> indexes = getIndexes(); |
|
291 |
for (int i = 0; i < indexes.size(); i++) |
|
292 |
{ |
|
293 |
Index index = indexes.get(i); |
|
294 |
Column[] cols = index.getColumns(); |
|
295 |
for (int j = 0; j < cols.length; j++) |
|
296 |
{ |
|
297 |
if (columns.get(cols[j].getColumnId())) |
|
298 |
{ |
|
299 |
result.add(index); |
|
300 |
break; |
|
301 |
} |
|
302 |
} |
|
303 |
} |
|
304 |
|
|
305 |
return result; |
|
306 |
} |
|
307 | ||
308 |
/** |
|
309 |
* Get all the ON UPDATE columns. |
|
310 |
* |
|
311 |
* @return The {@link #onUpdateColumns}. |
|
312 |
*/ |
|
313 |
public ArrayList<Integer> getOnUpdateColumns() |
|
314 |
{ |
|
315 |
return onUpdateColumns; |
|
316 |
} |
|
317 |
|
|
318 |
/** |
|
275 | 319 |
* Get an index by name. |
276 | 320 |
* |
277 | 321 |
* @param indexName the index name to search for |
... | ... | |
351 | 395 |
public abstract long getDiskSpaceUsed(); |
352 | 396 | |
353 | 397 |
/** |
398 |
* Check if this table can use in-place row copy with the UPDATE statement. |
|
399 |
* |
|
400 |
* @return <code>false</code> by default. |
|
401 |
*/ |
|
402 |
public boolean useInPlaceUpdate() |
|
403 |
{ |
|
404 |
return false; |
|
405 |
} |
|
406 |
|
|
407 |
/** |
|
354 | 408 |
* Get the row id column if this table has one. |
355 | 409 |
* |
356 | 410 |
* @return the row id column, or null |
... | ... | |
435 | 489 |
if (columnMap.size() > 0) { |
436 | 490 |
columnMap.clear(); |
437 | 491 |
} |
438 |
if (computedColumns.size() > 0) { |
|
492 |
if (computedColumns != null && computedColumns.size() > 0) {
|
|
439 | 493 |
computedColumns.clear(); |
440 | 494 |
} |
495 |
if (onUpdateColumns != null && onUpdateColumns.size() > 0) |
|
496 |
{ |
|
497 |
onUpdateColumns.clear(); |
|
498 |
} |
|
441 | 499 |
|
442 | 500 |
for (int i = 0; i < columns.length; i++) { |
443 | 501 |
Column col = columns[i]; |
... | ... | |
453 | 511 |
} |
454 | 512 |
columnMap.put(columnName, col); |
455 | 513 |
if (col.getComputed()) { |
514 |
if (computedColumns == null) |
|
515 |
{ |
|
516 |
computedColumns = new ArrayList<>(); |
|
517 |
} |
|
456 | 518 |
computedColumns.add(i); |
457 | 519 |
} |
520 |
if (col.getOnUpdateExpression() != null) |
|
521 |
{ |
|
522 |
if (onUpdateColumns == null) |
|
523 |
{ |
|
524 |
onUpdateColumns = new ArrayList<>(); |
|
525 |
} |
|
526 |
onUpdateColumns.add(i); |
|
527 |
} |
|
458 | 528 |
} |
459 | 529 |
} |
460 | 530 | |
... | ... | |
839 | 909 |
* |
840 | 910 |
* @param session the session |
841 | 911 |
* @param row the row |
912 |
* @param setCols When set, it contains the indexes of the columns changed via the UPDATE ... SET statement. |
|
842 | 913 |
*/ |
843 |
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 |
|
|
914 |
public void validateConvertUpdateSequence(Session session, Row row, BitSet setCols) { |
|
915 |
BitSet changed = null; |
|
916 |
if (setCols != null) |
|
917 |
{ |
|
918 |
changed = new BitSet(); |
|
919 |
changed.or(setCols); |
|
920 |
} |
|
921 |
|
|
922 |
if (computedColumns != null) |
|
923 |
{ |
|
924 |
for (int i = 0; i < computedColumns.size(); i++) |
|
925 |
{ |
|
926 |
int idx = computedColumns.get(i); |
|
927 |
row.setValue(idx, columns[idx].computeValue(session, row)); |
|
928 |
if (changed != null) |
|
929 |
{ |
|
930 |
changed.set(idx); |
|
931 |
} |
|
932 |
} |
|
933 |
} |
|
934 |
|
|
850 | 935 |
for (int i = 0; i < columns.length; i++) { |
936 |
if (changed != null && !changed.get(i)) |
|
937 |
{ |
|
938 |
// only changed columns |
|
939 |
continue; |
|
940 |
} |
|
941 |
|
|
851 | 942 |
Value value = row.getValue(i); |
852 | 943 |
Value v2 = columns[i].validateConvertUpdateSequence(session, value); |
853 | 944 |
if (v2 != value) { |
new/src/main/org/h2/table/TableLink.java 2022-12-02 16:02:23 +0000 | ||
---|---|---|
13 | 13 |
import java.sql.Statement; |
14 | 14 |
import java.sql.Types; |
15 | 15 |
import java.util.ArrayList; |
16 |
import java.util.BitSet; |
|
16 | 17 |
import java.util.HashMap; |
17 | 18 |
import java.util.List; |
18 | 19 |
import java.util.Objects; |
... | ... | |
694 | 695 |
* |
695 | 696 |
* @param session the session |
696 | 697 |
* @param row the row |
698 |
* @param setCols When set, it contains the indexes of the columns changed via the UPDATE ... SET statement. |
|
697 | 699 |
*/ |
698 | 700 |
@Override |
699 |
public void validateConvertUpdateSequence(Session session, Row row) { |
|
701 |
public void validateConvertUpdateSequence(Session session, Row row, BitSet setCols) {
|
|
700 | 702 |
for (int i = 0; i < columns.length; i++) { |
701 | 703 |
Value value = row.getValue(i); |
702 | 704 |
if (value != null) { |