Project

General

Profile

h2_update_perf3.patch

Constantin Asofiei, 12/02/2022 02:32 PM

Download (20.3 KB)

View differences:

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) {