DEV Community

Lord Neic
Lord Neic

Posted on

Multiple-Column Indexes and Hashing: The Ultimate Guide to Boosting Database Performance

Greetings, tech aficionados! Today, we're diving deep into the realms of multiple-column indexes and hashing—critical yet often overlooked aspects in the database world. As a Software Architect and a database enthusiast, I can tell you that understanding these concepts can make a world of difference in your application's performance. So grab your coffee, and let's get started!

What are Multiple-Column Indexes?

In database management systems like MySQL, PostgreSQL, or SQL Server, you can create indexes not just on a single column, but on multiple columns. These are known as composite or multi-column indexes.

CREATE INDEX composite_index
ON table_name (column1, column2);
Enter fullscreen mode Exit fullscreen mode

Why Hash a Composite Index?

Hashing a composite index is essentially a technique that involves converting a large composite key into a smaller, fixed-size value. Why do we do this? Simple: Performance. Hashing can substantially reduce the time it takes to search through a large set of data. When you hash a composite key, you're essentially creating a unique identifier for a record, which allows the database engine to retrieve the data in the blink of an eye. It's like having a VIP pass to skip the line at a concert—swift, efficient, and straight to the point.

How to Generate a Hashing

Generating a hash involves using a hash function that takes in an input (or 'message') and returns a fixed-size string of bytes. The output is typically a 'digest' that represents the data. Creating a hash isn't merely a call to a function; it's the transformation of data into a unique, fixed-size digest. This transformation is deterministic, meaning the same input will yield the same output, but even a minor change in the input creates a drastically different output.

-- MySQL example using MD5
UPDATE table_name 
SET hash_column = MD5(CONCAT(column1, column2));
Enter fullscreen mode Exit fullscreen mode

Types of Hashing

MD5

  • Use Case: Quick hash generation but not recommended for security-critical applications.
  • MD5, or Message Digest Algorithm 5, is a widely used cryptographic hash function that produces a 32-character hexadecimal number. Although it's blazing fast, it has vulnerabilities and is susceptible to collision attacks. Therefore, it's often relegated to non-critical applications where speed is a priority over security.

SHA-1

  • Use Case: Deprecated for most cryptographic uses but still acceptable for checksums.
  • SHA-1 or Secure Hash Algorithm 1, creates a 40-character digest and was once considered stronger than MD5. However, it's now considered broken and unsuitable for further use in security certificates, although it's still used in some checksum applications.

SHA-2

  • Use Case: Stronger than SHA-1 and MD5, suitable for most security-sensitive applications.
  • The SHA-2 (Secure Hash Algorithm 2) family consists of hash functions like SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, and SHA-512/256. It provides better security than its predecessors and is recommended for most security-sensitive operations.

CRC32

  • Use Case: Not cryptographic. Fast and suitable for checksums.
  • CRC32 or Cyclic Redundancy Check is a non-cryptographic hash function mainly used to check for accidental changes in raw data. It's blazingly fast but should not be used for any cryptographic applications.

Using CONCAT for Hashing

Concatenating fields is a common method for creating composite keys. The CONCAT\ function stitches together the values from multiple columns into a single string. This concatenated string serves as the input to your hash function. It's a straightforward yet powerful approach that helps you generate unique hashes even when individual column values are not unique.

-- Combining three columns
UPDATE table_name 
SET hash_column = MD5(CONCAT(column1, column2, column3));
Enter fullscreen mode Exit fullscreen mode

The Problem with CONCAT and NULL

The CONCAT()\ function usually returns NULL if any of the inputs is NULL. When dealing with NULL\ values, CONCAT\ can be tricky. A single NULL\ value in the concatenation process will yield a NULL\ output. This is problematic because you end up losing the other non-NULL values, rendering the hash useless.

Using CONCAT_WS

To tackle this, you can use CONCAT_WS\, which ignores NULL values. CONCAT_WS\ stands for CONCAT With Separator. It allows you to define a separator that will be inserted between the strings to be concatenated. More importantly, it skips over any NULL\ values, ensuring that your hash remains effective.

SELECT CONCAT_WS('|', 'Hello', NULL, 'World');  -- Returns 'Hello|World'
Enter fullscreen mode Exit fullscreen mode

What is "Generated Always As"?

The "Generated Always As" clause can be used to create a computed column. The "Generated Always As" clause is like having a virtual assistant who calculates the hash for you every time a new record is inserted or updated. It ensures that the hash column is always in sync with the columns it depends upon.

ALTER TABLE table_name
ADD hash_column CHAR(32) GENERATED ALWAYS AS (MD5(CONCAT(column1, column2))) STORED;
Enter fullscreen mode Exit fullscreen mode

Choosing the Cache Type

For MD5, you'll most likely want to use CHAR(32)\. Choosing the correct data type for storing your hash can be pivotal. For MD5 hashes, CHAR(32)\ is generally recommended over VARCHAR(32)\ because the size is fixed, which allows MySQL to optimize space allocation.

Optimizing with UNHEX and Binary Types

To further optimize space, you can use the UNHEX\ function and change the column type to BINARY(16)\. Converting the hash to a binary format via UNHEX\ can further optimize storage. This conversion slashes the storage size in half, making your database lighter and faster.

ALTER TABLE table_name
ADD hash_column BINARY(16) GENERATED ALWAYS AS (UNHEX(MD5(CONCAT(column1, column2)))) STORED;
Enter fullscreen mode Exit fullscreen mode

Querying the Index

SELECT * FROM table_name WHERE hash_column = UNHEX(MD5('value1value2'));
Enter fullscreen mode Exit fullscreen mode

When querying, using UNHEX\ can help you directly compare binary hash values, making queries faster. It's the final piece in a well-optimized puzzle, allowing you to reap the benefits of all previous optimizations.

Speed Comparisons

You'll find that MD5 and SHA-1 are generally faster than SHA-2. CRC32 is the fastest but least secure. Conducting speed tests on different hash algorithms with varying numbers of rows can be eye-opening. Generally, you'll find that as the number of rows scales up, hash algorithms like MD5 and SHA-1 maintain consistent speed, while SHA-2 tends to slow down a bit.

Top comments (1)

Collapse
 
fschtn profile image
Francesco Schettino

It looks very interesting (specially the “Generated always as” that I didn’t knew)!

But, when I have to do a SELECT, do I have to generate the hash externally? In other words, the application should be responsible for generating it, right?