We use the same basic operation to

Analysing Runtimes

Also note that:

  • = Number of records in the table
  • = Number of pages in the table

Selection

Recall what a selection is: Selection

In order to optimise a selection, a DBMS needs to know:

  1. All available indexes/pointers to records (to actually find the files)
  2. The expected size of the result i.e. number of pages (to know when to stop looking)
Reducing Factor

A DBMS uses it’s optimiser to estimate the expected size using reducing factors, which is a fraction per predicate, which is a basic relational statement (i.e. involves only one relational comparators):

  • : Reducing factor (also called selectivity), a fraction that states how many records satisfy the associated predicate
  • : Number of predicates/ basic relational statements

Once the estimated size is obtained, the DBMS can search through the database file structure to find records matching the selection criteria:

Searching: Heap Files

For heap files (unsorted files), it needs to linear search through every single record so it doesn’t miss anything.

%%🖋 Edit in Excalidraw, and the dark exported image%%

Searching: Sorted Files

For sorted files, the DBMS can simply binary search to find the first record matching condition, then iterate till until the estimate size of records have been scanned

%%🖋 Edit in Excalidraw, and the dark exported image%%

Searching Through Indexed Files

First the DBMS needs to traverse through the B+ Tree (or use the hash table if the index is hash-based) to find indexes that satisfy the criteria.

Hash-based Indexes Only Work On Equality Checks!

Hash-based

Then, linearly iterate through the indexes (note that the indexes would be stored in their own pages, which need to be brought to RAM). Let be the number of pages occupied by indexes.#todo B-tree has leaf nodes doubly linked.

If the indexes are clustered, then the total cost is:

Why? Every index points to a record, and so if only records satisfy the criteria, then we only need to iterate through indexes. For each index, we need to load up the page it points to, but because the indexes are clustered, many of the indexes point to the same page.

If the indexes are unclustered, then the total cost is:

This is because the index lookup does not guarantee indexes pointing to records in the same page (due to the unclustered nature), so we need do an I/O operation for each record (total records).

Composed Selection Criteria

In most cases, the selection criteria contains multiple predicates, involving multiple attributes/columns.

A tree-based index can only match predicates that involve attributes in the prefix of the search key fields . What this means is that if we have a composed selection criteria of multiple predicates, an indexed database is only useful if at least one predicate has any of the prefixes of the search key field(s)

Predicates must involve search key field to be optimised

If we have a index on column , we can only optimise selections if they have predicates involving .

On the fly checking is checking predicates that are not part of the search key fields. It does not contribute to the I/O cost, because the page is already loaded when checking from key fields.

In terms of reduction factors, we only have them if the predicates involve search keys:

Example: Three different selections

Assume we have a database containing columns with a unclustered index on column (i.e. our indexes are ) Look at the following selection criteria:


  • The size estimations would be as follows:
  1. : Reduction factor involving , because is a prefix of
  2. : Reduction factors involving both and , because is a prefix of
  3. : No reduction factors, because is not a prefix of . Would need to perform a full table scan
  4. : Reduction factors only involve and and needs to be checked on the fly
  5. : No reduction factors because is not in

Projection

Recall projection is:

Projection

Notice the Duplicate elimination warning? That’s what the DBMS aims to optimise: elimination of duplicates

Projecting Factor

Like with the reducing factor for selection, the projecting factor is a ratio of projected columns to total columns, which lets us know what percentage of attributes are being projected. The DBMS uses this to estimate how much space the final output will take.

This can be done by either sorting the data using sorting algorithms (which guarantees duplicates are next to each other) or use hash collisions

The main complication with sorting is that the data stored in disk is mostly much larger than the maximum space in RAM (where it can be processed). To solve this, we use an external sorting algorithm.

External Merge

The main method for this is with external merge sort:

Process

In terms of O operations, external merge sort over has a runtime of:

There is a factor of two because we need to both read & write data when sorting.

Hash-based

#todo

Operation Cost

The total cost to remove duplicates with external merge sort is:

  1. Reading the entire table, so we only keep the attributes that need to be projected:
  2. Write pages with projected attributes to disk:
  3. Sort pages with projected attributes using external merge sort:
  4. Read sorted projected pages to discard adjacent duplicates:

Joins

Joins

Joins are generally very expensive to perform, and in the worst case result in the cross product. Out of all the possible joins, the natural join is the most commonly queried one, and hence need to be the most optimised. A RDBMS can implement a join in many ways:

  • Nested-loop joins
    • Simple nested-loop join
    • Page based nested-loop join
    • Block nested-loop join
  • Sort-merge join
  • Hash join
Simple Nested-Loop Join

The most straight-forward method of performing a natural join over unsorted files is using a nested for-loop:

Algorithm SimpleNestedLoopJoin(A: Table, B: Table):
	For each tuple a in A do:
		For each tuple b in B do:
			If a.attribute == b.attribute then:
				Add (a,b) to the Join
			End If
		End do
	End do
	Return Join
End Algorithm

The final join is unsorted.

Total cost:

Page-based Nested-Loop Join

Page-based nested-loop joins exploit the fact that multiple tuples are stored in a single page, so we can start by loading a page and then comparing every tuple in that page in order to minimise the number of I/O operations.

Algorithm PageBasedNestedLoopJoin(A: Table, B: Table):
	For each page a_page in A do:
		For each page b_page in B do:
			For each tuple a in A do:
				For each tuple b in B do:
					If a.attribute == b.attribute then:
						Add (a,b) to the Join
					End If
				End do
			End do
		End do
	End do
	Return Join
End Algorithm

The final join is unsorted.

Because we measure our total runtime in terms of page accesses, once we access a page, any tuples in it do not add extra time. Hence the final cost is:

Block Nested-Loop Join

A further optimisation is to exploit as much main memory (RAM) available. The block-based nested-loop join first reserves two pages in the RAM for an input and output buffer to reduce the number of I/O operations.

Assume we are treating as the outer loop table, and as the inner loop table

If the maximum capacity of the RAM (in pages) is , then the block-based approach reads in sized ‘blocks’ of pages of , comparing it with the input buffer for . If the join conditions are satisfied, then writes to the output buffer page.

  • Whenever the block is fully read, read in a new block (of ) from disk
  • Whenever the input buffer is read, read in a new page (of ) from disk
  • Whenever the output buffer is full, flush it (write to disk)

%%🖋 Edit in Excalidraw, and the dark exported image%%

The total I/O operations are:

Sort-Merge Join

The sort-merge join is an improvement over nested-loop joins, because once and are sorted, there is no need to check all of for a single value of , since if we have a value , every value of after fails to satisfy the condition, so we can stop checking.

The ‘merging’ operation is simply taking the column values of and and merging them to form the join .

is always only scanned once, and (approximately), is scanned once. Technically, there would be multiple scans when has duplicates, but the amortised cost is that is only scanned once.

The total I/O cost is:

Assuming we use external merge sort:

Hash Join

#todo

General Join Conditions

For composed join conditions that consists of only equality checks, i.e. equi-joins, we can use any of the join techniques, with sort-merge join and hash join giving the best runtimes:

  • For sort-merge join, sort over the combination of columns in and . For example, if we have the join: we sort with the combination of (this can be done by string concatenation, for example), and sort over .
  • For hash join, we partition over the combination of columns

For joins containing inequalities in their join conditions, i.e. theta-joins, we cannot* use hash join or sort-merge join! The best option is to use block nested-loop join.