Project

General

Profile

h2_update_perf2.patch

Constantin Asofiei, 11/28/2022 10:07 AM

Download (16.4 KB)

View differences:

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