SQL Performance Explained Markus Winand 2024 scribd download
SQL Performance Explained Markus Winand 2024 scribd download
https://ebookultra.com
https://ebookultra.com/download/sql-performance-
explained-markus-winand/
https://ebookultra.com/download/building-performance-dashboards-and-
balanced-scorecards-with-sql-server-reporting-services-1st-edition-
knight/
ebookultra.com
https://ebookultra.com/download/modern-fortran-in-practice-markus/
ebookultra.com
https://ebookultra.com/download/sql-server-t-sql-recipes-4th-edition-
jason-brimhall/
ebookultra.com
https://ebookultra.com/download/microsoft-sql-server-2012-t-sql-1st-
edition-tom-coffing/
ebookultra.com
European Democracies 9th Edition Markus M.L. Crepaz
https://ebookultra.com/download/european-democracies-9th-edition-
markus-m-l-crepaz/
ebookultra.com
https://ebookultra.com/download/modernizing-enterprise-java-1st-
edition-markus-eisele/
ebookultra.com
https://ebookultra.com/download/essential-sql-on-sql-server-2008-1st-
edition-dr-sikha-bagui/
ebookultra.com
https://ebookultra.com/download/myths-legends-explained-neil-philip/
ebookultra.com
https://ebookultra.com/download/joe-celko-s-sql-for-smarties-fourth-
edition-advanced-sql-programming-joe-celko/
ebookultra.com
SQL Performance Explained Markus Winand Digital
Instant Download
Author(s): Markus Winand
ISBN(s): 9783950307825, 3950307826
Edition: Paperback
File Details: PDF, 1.17 MB
Year: 2012
Language: english
MA CO
JOR VER
SQ S A
L D LL
ATA
BA
SQL SES
PERFORMANCE
EXPLAINED
ENGLISH EDITION
MARKUS WINAND
License Agreement
This ebook is licensed for your personal enjoyment only. This ebook may
not be re-sold or given away to other people. If you would like to share
this book with another person, please purchase an additional copy for each
person. If you’re reading this book and did not purchase it, or it was not
purchased for your use only, then please return to
http://SQL-Performance-Explained.com/
and purchase your own copy. Thank you for respecting the hard work of
the author.
Publisher:
Markus Winand
Maderspergerstasse 1-3/9/11
1160 Wien
AUSTRIA
<[email protected]>
While every precaution has been taken in the preparation of this book, the
publisher and author assume no responsibility for errors and omissions, or
for damages resulting from the use of the information contained herein.
The book solely reflects the author’s views. The database vendors men-
tioned have neither supported the work financially nor verified the content.
Cover design:
tomasio.design — Mag. Thomas Weninger — Wien — Austria
Cover photo:
Brian Arnold — Turriff — UK
Copy editor:
Nathan Ingvalson — Graz — Austria
2014-08-26
SQL Performance Explained
Markus Winand
Vienna, Austria
Contents
Preface ............................................................................................ vi
iv
SQL Performance Explained
v
Preface
SELECT date_of_birth
FROM employees
WHERE last_name = 'WINAND'
The SQL query reads like an English sentence that explains the requested
data. Writing SQL statements generally does not require any knowledge
about inner workings of the database or the storage system (such as disks,
files, etc.). There is no need to tell the database which files to open or how
to find the requested rows. Many developers have years of SQL experience
yet they know very little about the processing that happens in the database.
It turns out that the only thing developers need to learn is how to index.
Database indexing is, in fact, a development task. That is because the
most important information for proper indexing is not the storage system
configuration or the hardware setup. The most important information for
indexing is how the application queries the data. This knowledge —about
vi
Preface: Developers Need to Index
This book covers everything developers need to know about indexes — and
nothing more. To be more precise, the book covers the most important
index type only: the B-tree index.
The B-tree index works almost identically in many databases. The book only
uses the terminology of the Oracle® database, but the principles apply to
other databases as well. Side notes provide relevant information for MySQL,
PostgreSQL and SQL Server®.
This chapter makes up the main body of the book. Once you learn to
use these techniques, you will write much faster SQL.
vii
Preface: Developers Need to Index
viii
Chapter 1
Anatomy of an Index
“An index makes the query fast” is the most basic explanation of an index I
have ever seen. Although it describes the most important aspect of an index
very well, it is —unfortunately—not sufficient for this book. This chapter
describes the index structure in a less superficial way but doesn’t dive too
deeply into details. It provides just enough insight for one to understand
the SQL performance aspects discussed throughout the book.
Clustered Indexes
SQL Server and MySQL (using InnoDB) take a broader view of what
“index” means. They refer to tables that consist of the index structure
only as clustered indexes. These tables are called Index-Organized
Tables (IOT) in the Oracle database.
Chapter 5, “Clustering Data”, describes them in more detail and
explains their advantages and disadvantages.
1
Chapter 1: Anatomy of an Index
The database combines two data structures to meet the challenge: a doubly
linked list and a search tree. These two structures explain most of the
database’s performance characteristics.
The logical order is established via a doubly linked list. Every node has links
to two neighboring entries, very much like a chain. New nodes are inserted
between two existing nodes by updating their links to refer to the new
node. The physical location of the new node doesn’t matter because the
doubly linked list maintains the logical order.
The data structure is called a doubly linked list because each node refers
to the preceding and the following node. It enables the database to read
the index forwards or backwards as needed. It is thus possible to insert
new entries without moving large amounts of data—it just needs to change
some pointers.
Doubly linked lists are also used for collections (containers) in many
programming languages.
2
The Index Leaf Nodes
Databases use doubly linked lists to connect the so-called index leaf nodes.
Each leaf node is stored in a database block or page; that is, the database’s
smallest storage unit. All index blocks are of the same size —typically a few
kilobytes. The database uses the space in each block to the extent possible
and stores as many index entries as possible in each block. That means
that the index order is maintained on two different levels: the index entries
within each leaf node, and the leaf nodes among each other using a doubly
linked list.
lu 1
lu 2
lu 3
4
mn
co mn
co mn
co mn
mn
D
lu
WI
lu
co
RO
co
11 3C AF A 34 1 2
13 F3 91 A 27 5 9
18 6F B2
A 39 2 5
X 21 7 2
21 2C 50
27 0F 1B A 11 1 6
27 52 55
A 35 8 3
X 27 3 2
34 0D 1E
35 44 53 A 18 3 6
39 24 5D A 13 7 4
Figure 1.1 illustrates the index leaf nodes and their connection to the table
data. Each index entry consists of the indexed columns (the key, column 2)
and refers to the corresponding table row (via ROWID or RID). Unlike the
index, the table data is stored in a heap structure and is not sorted at all.
There is neither a relationship between the rows stored in the same table
block nor is there any connection between the blocks.
3
Chapter 1: Anatomy of an Index
es
Branch Node Leaf Nodes
od
es
e
N
od
od
ch
N
N
40 4A 1B
an
t
af
o
Ro
Le
Br
43 9F 71
46 A2 D2 11 3C AF
13 F3 91
18 6F B2
21 2C 50
18 27 0F 1B
27 27 52 55
39
46 8B 1C 34 0D 1E
35 44 53
39 24 5D
53 A0 A1 40 4A 1B
43 9F 71
53 0D 79 46 A2 D2
46 46 8B 1C
53 A0 A1
53 0D 79
53 39
46
53
57 55 9C F6
57 55 9C F6 83
98
83 57 B1 C1
57 50 29
83 57 B1 C1 67 C4 6B
83 FF 9D
83 AF E9
57 50 29 84 80 64
86 4C 2F
88 06 5B
89 6A 3E
88 90 7D 9A
94 94 36 D4
67 C4 6B 98
95 EA 37
98 5E B2
83 FF 9D 98 D8 4F
83 AF E9
Figure 1.2 shows an example index with 30 entries. The doubly linked list
establishes the logical order between the leaf nodes. The root and branch
nodes support quick searching among the leaf nodes.
The figure highlights a branch node and the leaf nodes it refers to. Each
branch node entry corresponds to the biggest value in the respective leaf
node. That is, 46 in the first leaf node so that the first branch node entry
is also 46. The same is true for the other leaf nodes so that in the end the
4
The Search Tree (B-Tree)
branch node has the values 46, 53, 57 and 83. According to this scheme, a
branch layer is built up until all the leaf nodes are covered by a branch node.
The next layer is built similarly, but on top of the first branch node level.
The procedure repeats until all keys fit into a single node, the root node.
The structure is a balanced search tree because the tree depth is equal at
every position; the distance between root node and leaf nodes is the same
everywhere.
Note
A B-tree is a balanced tree—not a binary tree.
46 8B 1C
53 A0 A1
46 53 0D 79
39
53
83
57
98 55 9C F6
83
57 B1 C1
57 50 29
Figure 1.3 shows an index fragment to illustrate a search for the key “57”.
The tree traversal starts at the root node on the left-hand side. Each entry
is processed in ascending order until a value is greater than or equal to (>=)
the search term (57). In the figure it is the entry 83. The database follows
the reference to the corresponding branch node and repeats the procedure
until the tree traversal reaches a leaf node.
Important
The B-tree enables the database to find a leaf node quickly.
5
Chapter 1: Anatomy of an Index
The first ingredient for a slow index lookup is the leaf node chain. Consider
the search for “57” in Figure 1.3 again. There are obviously two matching
entries in the index. At least two entries are the same, to be more precise:
the next leaf node could have further entries for “57”. The database must
read the next leaf node to see if there are any more matching entries. That
means that an index lookup not only needs to perform the tree traversal,
it also needs to follow the leaf node chain.
The second ingredient for a slow index lookup is accessing the table.
Even a single leaf node might contain many hits — often hundreds. The
corresponding table data is usually scattered across many table blocks (see
Figure 1.1, “Index Leaf Nodes and Corresponding Table Data”). That means
that there is an additional table access for each hit.
An index lookup requires three steps: (1) the tree traversal; (2) following the
leaf node chain; (3) fetching the table data. The tree traversal is the only
step that has an upper bound for the number of accessed blocks—the index
depth. The other two steps might need to access many blocks—they cause
a slow index lookup.
6
Slow Indexes, Part I
Logarithmic Scalability
In mathematics, the logarithm of a number to a given base is the
power or exponent to which the base must be raised in order to
1
produce the number [Wikipedia ].
In a search tree the base corresponds to the number of entries per
branch node and the exponent to the tree depth. The example index
in Figure 1.2 holds up to four entries per node and has a tree depth
3
of three. That means that the index can hold up to 64 (4 ) entries. If
4
it grows by one level, it can already hold 256 entries (4 ). Each time
a level is added, the maximum number of index entries quadruples.
The logarithm reverses this function. The tree depth is therefore
log4(number-of-index-entries).
The logarithmic growth enables
Tree Depth Index Entries
the example index to search a
million records with ten tree 3 64
levels, but a real world index is 4 256
even more efficient. The main 5 1,024
factor that affects the tree depth,
6 4,096
and therefore the lookup perfor-
mance, is the number of entries 7 16,384
in each tree node. This number 8 65,536
corresponds to— mathematically
9 262,144
speaking — the basis of the loga-
rithm. The higher the basis, the 10 1,048,576
shallower the tree, the faster the
traversal.
Databases exploit this concept to a maximum extent and put as many
entries as possible into each node— often hundreds. That means that
every new index level supports a hundred times more entries.
1
http://en.wikipedia.org/wiki/Logarithm
7
Chapter 1: Anatomy of an Index
The origin of the “slow indexes” myth is the misbelief that an index lookup
just traverses the tree, hence the idea that a slow index must be caused by a
“broken” or “unbalanced” tree. The truth is that you can actually ask most
databases how they use an index. The Oracle database is rather verbose in
this respect and has three distinct operations that describe a basic index
lookup:
The important point is that an INDEX RANGE SCAN can potentially read a large
part of an index. If there is one more table access for each row, the query
can become slow even when using an index.
8
Chapter 2
The where clause defines the search condition of an SQL statement, and it
thus falls into the core functional domain of an index: finding data quickly.
Although the where clause has a huge impact on performance, it is often
phrased carelessly so that the database has to scan a large part of the index.
The result: a poorly written where clause is the first ingredient of a slow
query.
This chapter explains how different operators affect index usage and how
to make sure that an index is usable for as many queries as possible. The
last section shows common anti-patterns and presents alternatives that
deliver better performance.
This section shows how to verify index usage and explains how
concatenated indexes can optimize combined conditions. To aid
understanding, we will analyze a slow query to see the real world impact
of the causes explained in Chapter 1.
9
Chapter 2: The Where Clause
Primary Keys
We start with the simplest yet most common where clause: the primary key
lookup. For the examples throughout this chapter we use the EMPLOYEES
table defined as follows:
The database automatically creates an index for the primary key. That
means there is an index on the EMPLOYEE_ID column, even though there is
no create index statement.
The following query uses the primary key to retrieve an employee’s name:
The where clause cannot match multiple rows because the primary key
constraint ensures uniqueness of the EMPLOYEE_ID values. The database does
not need to follow the index leaf nodes —it is enough to traverse the index
tree. We can use the so-called execution plan for verification:
---------------------------------------------------------------
|Id |Operation | Name | Rows | Cost |
---------------------------------------------------------------
| 0 |SELECT STATEMENT | | 1 | 2 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 1 | 2 |
|*2 | INDEX UNIQUE SCAN | EMPLOYEES_PK | 1 | 1 |
---------------------------------------------------------------
10
Primary Keys
The Oracle execution plan shows an INDEX UNIQUE SCAN — the operation that
only traverses the index tree. It fully utilizes the logarithmic scalability of
the index to find the entry very quickly —almost independent of the table
size.
Tip
The execution plan (sometimes explain plan or query plan) shows the
steps the database takes to execute an SQL statement. Appendix A on
page 165 explains how to retrieve and read execution plans with
other databases.
After accessing the index, the database must do one more step to
fetch the queried data (FIRST_NAME, LAST_NAME) from the table storage:
the TABLE ACCESS BY INDEX ROWID operation. This operation can become a
performance bottleneck —as explained in “Slow Indexes, Part I”— but there
is no such risk in connection with an INDEX UNIQUE SCAN. This operation
cannot deliver more than one entry so it cannot trigger more than one table
access. That means that the ingredients of a slow query are not present
with an INDEX UNIQUE SCAN.
11
Chapter 2: The Where Clause
Concatenated Indexes
Even though the database creates the index for the primary key
automatically, there is still room for manual refinements if the key consists
of multiple columns. In that case the database creates an index on all
primary key columns — a so-called concatenated index (also known as multi-
column, composite or combined index). Note that the column order of a
concatenated index has great impact on its usability so it must be chosen
carefully.
The index for the new primary key is therefore defined in the following way:
A query for a particular employee has to take the full primary key into
account— that is, the SUBSIDIARY_ID column also has to be used:
12
Concatenated Indexes
Whenever a query uses the complete primary key, the database can use
an INDEX UNIQUE SCAN — no matter how many columns the index has. But
what happens when using only one of the key columns, for example, when
searching all employees of a subsidiary?
----------------------------------------------------
| Id | Operation | Name | Rows | Cost |
----------------------------------------------------
| 0 | SELECT STATEMENT | | 106 | 478 |
|* 1 | TABLE ACCESS FULL| EMPLOYEES | 106 | 478 |
----------------------------------------------------
The execution plan reveals that the database does not use the index. Instead
it performs a FULL TABLE SCAN. As a result the database reads the entire table
and evaluates every row against the where clause. The execution time grows
with the table size: if the table grows tenfold, the FULL TABLE SCAN takes ten
times as long. The danger of this operation is that it is often fast enough
in a small development environment, but it causes serious performance
problems in production.
13
Chapter 2: The Where Clause
The database does not use the index because it cannot use single columns
from a concatenated index arbitrarily. A closer look at the index structure
makes this clear.
A concatenated index is just a B-tree index like any other that keeps the
indexed data in a sorted list. The database considers each column according
to its position in the index definition to sort the index entries. The first
column is the primary sort criterion and the second column determines the
order only if two entries have the same value in the first column and so on.
Important
A concatenated index is one index across multiple columns.
Index-Tree
D
_I
RY
EE
IA
D
OY
ID
_I
D
PL
BS
_I
_I
RY
D
EM
SU
_I
RY
EE
IA
EE
IA
OY
ID
123 20 ROWID
OY
ID
PL
BS
PL
BS
EM
SU
123 21 ROWID
EM
SU
The index excerpt in Figure 2.1 shows that the entries for subsidiary 20 are
not stored next to each other. It is also apparent that there are no entries
with SUBSIDIARY_ID = 20 in the tree, although they exist in the leaf nodes.
The tree is therefore useless for this query.
14
Concatenated Indexes
Tip
Visualizing an index helps in understanding what queries the index
supports. You can query the database to retrieve the entries in index
order (SQL:2008 syntax, see page 144 for proprietary solutions
using LIMIT, TOP or ROWNUM):
If you put the index definition and table name into the query, you
will get a sample from the index. Ask yourself if the requested rows
are clustered in a central place. If not, the index tree cannot help find
that place.
We can take advantage of the fact that the first index column is always
usable for searching. Again, it is like a telephone directory: you don’t need
to know the first name to search by last name. The trick is to reverse the
index column order so that the SUBSIDIARY_ID is in the first position:
Both columns together are still unique so queries with the full primary
key can still use an INDEX UNIQUE SCAN but the sequence of index entries is
entirely different. The SUBSIDIARY_ID has become the primary sort criterion.
That means that all entries for a subsidiary are in the index consecutively
so the database can use the B-tree to find their location.
15
Chapter 2: The Where Clause
Important
The most important consideration when defining a concatenated
index is how to choose the column order so it can be used as often
as possible.
The execution plan confirms that the database uses the “reversed” index.
The SUBSIDIARY_ID alone is not unique anymore so the database must
follow the leaf nodes in order to find all matching entries: it is therefore
using the INDEX RANGE SCAN operation.
--------------------------------------------------------------
|Id |Operation | Name | Rows | Cost |
--------------------------------------------------------------
| 0 |SELECT STATEMENT | | 106 | 75 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 106 | 75 |
|*2 | INDEX RANGE SCAN | EMPLOYEE_PK | 106 | 2 |
--------------------------------------------------------------
Even though the two-index solution delivers very good select performance
as well, the single-index solution is preferable. It not only saves storage
space, but also the maintenance overhead for the second index. The fewer
indexes a table has, the better the insert, delete and update performance.
To define an optimal index you must understand more than just how
indexes work — you must also know how the application queries the data.
This means you have to know the column combinations that appear in the
where clause.
16
Concatenated Indexes
The only place where the technical database knowledge meets the
functional knowledge of the business domain is the development
department. Developers have a feeling for the data and know the access
path. They can properly index to get the best benefit for the overall
application without much effort.
17
Chapter 2: The Where Clause
The adopted EMPLOYEE_PK index improves the performance of all queries that
search by subsidiary only. It is however usable for all queries that search
by SUBSIDIARY_ID — regardless of whether there are any additional search
criteria. That means the index becomes usable for queries that used to use
another index with another part of the where clause. In that case, if there
are multiple access paths available it is the optimizer’s job to choose the
best one.
18
Slow Indexes, Part II
---------------------------------------------------------------
|Id |Operation | Name | Rows | Cost |
---------------------------------------------------------------
| 0 |SELECT STATEMENT | | 1 | 30 |
|*1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 1 | 30 |
|*2 | INDEX RANGE SCAN | EMPLOYEES_PK | 40 | 2 |
---------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("LAST_NAME"='WINAND')
2 - access("SUBSIDIARY_ID"=30)
The execution plan uses an index and has an overall cost value of 30.
So far, so good. It is however suspicious that it uses the index we just
changed— that is enough reason to suspect that our index change caused
the performance problem, especially when bearing the old index definition
in mind— it started with the EMPLOYEE_ID column which is not part of the
where clause at all. The query could not use that index before.
For further analysis, it would be nice to compare the execution plan before
and after the change. To get the original execution plan, we could just
deploy the old index definition again, however most databases offer a
simpler method to prevent using an index for a specific query. The following
example uses an Oracle optimizer hint for that purpose.
19
Chapter 2: The Where Clause
The execution plan that was presumably used before the index change did
not use an index at all:
----------------------------------------------------
| Id | Operation | Name | Rows | Cost |
----------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 477 |
|* 1 | TABLE ACCESS FULL| EMPLOYEES | 1 | 477 |
----------------------------------------------------
Even though the TABLE ACCESS FULL must read and process the entire table,
it seems to be faster than using the index in this case. That is particularly
unusual because the query matches one row only. Using an index to find
a single row should be much faster than a full table scan, but in this case
it is not. The index seems to be slow.
Tip
Appendix A, “Execution Plans”, explains how to find the “Predicate
Information” for other databases.
The INDEX RANGE SCAN with operation ID 2 (Example 2.1 on page 19)
applies only the SUBSIDIARY_ID=30 filter. That means that it traverses the
index tree to find the first entry for SUBSIDIARY_ID 30. Next it follows the
leaf node chain to find all other entries for that subsidiary. The result of the
INDEX RANGE SCAN is a list of ROWIDs that fulfill the SUBSIDIARY_ID condition:
depending on the subsidiary size, there might be just a few ones or there
could be many hundreds.
The next step is the TABLE ACCESS BY INDEX ROWID operation. It uses the
ROWIDs from the previous step to fetch the rows —all columns— from the
table. Once the LAST_NAME column is available, the database can evaluate
the remaining part of the where clause. That means the database has to
fetch all rows for SUBSIDIARY_ID=30 before it can apply the LAST_NAME filter.
20
Slow Indexes, Part II
The statement’s response time does not depend on the result set size
but on the number of employees in the particular subsidiary. If the
subsidiary has just a few members, the INDEX RANGE SCAN provides better
performance. Nonetheless a TABLE ACCESS FULL can be faster for a huge
subsidiary because it can read large parts from the table in one shot (see
“Full Table Scan” on page 13).
The query is slow because the index lookup returns many ROWIDs — one for
each employee of the original company— and the database must fetch them
individually. It is the perfect combination of the two ingredients that make
an index slow: the database reads a wide index range and has to fetch many
rows individually.
Choosing the best execution plan depends on the table’s data distribution
as well so the optimizer uses statistics about the contents of the database.
In our example, a histogram containing the distribution of employees over
subsidiaries is used. This allows the optimizer to estimate the number
of rows returned from the index lookup —the result is used for the cost
calculation.
Statistics
A cost-based optimizer uses statistics about tables, columns, and
indexes. Most statistics are collected on the column level: the number
of distinct values, the smallest and largest values (data range),
the number of NULL occurrences and the column histogram (data
distribution). The most important statistical value for a table is its
size (in rows and blocks).
The most important index statistics are the tree depth, the number
of leaf nodes, the number of distinct keys and the clustering factor
(see Chapter 5, “Clustering Data”).
The optimizer uses these values to estimate the selectivity of the
where clause predicates.
21
Chapter 2: The Where Clause
If there are no statistics available— for example because they were deleted—
the optimizer uses default values. The default statistics of the Oracle
database suggest a small index with medium selectivity. They lead to the
estimate that the INDEX RANGE SCAN will return 40 rows. The execution plan
shows this estimation in the Rows column (again, see Example 2.1 on page
19). Obviously this is a gross underestimate, as there are 1000 employees
working for this subsidiary.
---------------------------------------------------------------
|Id |Operation | Name | Rows | Cost |
---------------------------------------------------------------
| 0 |SELECT STATEMENT | | 1 | 680 |
|*1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 1 | 680 |
|*2 | INDEX RANGE SCAN | EMPLOYEES_PK | 1000 | 4 |
---------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("LAST_NAME"='WINAND')
2 - access("SUBSIDIARY_ID"=30)
The cost value of 680 is even higher than the cost value for the execution
plan using the FULL TABLE SCAN (477, see page 20). The optimizer will
therefore automatically prefer the FULL TABLE SCAN.
This example of a slow index should not hide the fact that proper indexing
is the best solution. Of course searching on last name is best supported by
an index on LAST_NAME:
22
Slow Indexes, Part II
--------------------------------------------------------------
| Id | Operation | Name | Rows | Cost |
--------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 3 |
|* 1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 1 | 3 |
|* 2 | INDEX RANGE SCAN | EMP_NAME | 1 | 1 |
--------------------------------------------------------------
The two execution plans from Example 2.1 (page 19) and Example 2.2
are almost identical. The database performs the same operations and
the optimizer calculated similar cost values, nevertheless the second plan
performs much better. The efficiency of an INDEX RANGE SCAN may vary
over a wide range —especially when followed by a table access. Using an
index does not automatically mean a statement is executed in the best way
possible.
23
Chapter 2: The Where Clause
Functions
The index on LAST_NAME has improved the performance considerably, but
it requires you to search using the same case (upper/lower) as is stored in
the database. This section explains how to lift this restriction without a
decrease in performance.
Note
MySQL 5.6 does not support function-based indexing as described
below. As an alternative, virtual columns were planned for MySQL
6.0 but were introduced in MariaDB 5.2 only.
Regardless of the capitalization used for the search term or the LAST_NAME
column, the UPPER function makes them match as desired.
Note
Another way for case-insensitive matching is to use a different
“collation”. The default collations used by SQL Server and MySQL do
not distinguish between upper and lower case letters— they are case-
insensitive by default.
24
Case-Insensitive Search Using UPPER or LOWER
The logic of this query is perfectly reasonable but the execution plan is not:
----------------------------------------------------
| Id | Operation | Name | Rows | Cost |
----------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 477 |
|* 1 | TABLE ACCESS FULL| EMPLOYEES | 10 | 477 |
----------------------------------------------------
It is a return of our old friend the full table scan. Although there is an index
on LAST_NAME, it is unusable —because the search is not on LAST_NAME but
on UPPER(LAST_NAME). From the database’s perspective, that’s something
entirely different.
This is a trap we all might fall into. We recognize the relation between
LAST_NAME and UPPER(LAST_NAME) instantly and expect the database to “see”
it as well. In reality the optimizer’s view is more like this:
The UPPER function is just a black box. The parameters to the function
are not relevant because there is no general relationship between the
function’s parameters and the result.
Tip
Replace the function name with BLACKBOX to understand the opti-
mizer’s point of view.
25
Chapter 2: The Where Clause
To support that query, we need an index that covers the actual search term.
That means we do not need an index on LAST_NAME but on UPPER(LAST_NAME):
The database can use a function-based index if the exact expression of the
index definition appears in an SQL statement —like in the example above.
The execution plan confirms this:
--------------------------------------------------------------
|Id |Operation | Name | Rows | Cost |
--------------------------------------------------------------
| 0 |SELECT STATEMENT | | 100 | 41 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 100 | 41 |
|*2 | INDEX RANGE SCAN | EMP_UP_NAME | 40 | 1 |
--------------------------------------------------------------
Warning
Sometimes ORM tools use UPPER and LOWER without the developer’s
knowledge. Hibernate, for example, injects an implicit LOWER for case-
insensitive searches.
The execution plan is not yet the same as it was in the previous section
without UPPER; the row count estimate is too high. It is particularly strange
that the optimizer expects to fetch more rows from the table than the
INDEX RANGE SCAN delivers in the first place. How can it fetch 100 rows from
the table if the preceding index scan returned only 40 rows? The answer is
that it can not. Contradicting estimates like this often indicate problems
with the statistics. In this particular case it is because the Oracle database
26
Case-Insensitive Search Using UPPER or LOWER
does not update the table statistics when creating a new index (see also
“Oracle Statistics for Function-Based Indexes” on page 28).
--------------------------------------------------------------
|Id |Operation | Name | Rows | Cost |
--------------------------------------------------------------
| 0 |SELECT STATEMENT | | 1 | 3 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 1 | 3 |
|*2 | INDEX RANGE SCAN | EMP_UP_NAME | 1 | 1 |
--------------------------------------------------------------
Note
The so-called “extended statistics” on expressions and column groups
were introduced with Oracle release 11g.
Tip
Appendix A, “Execution Plans”, describes the row count estimates in
SQL Server and PostgreSQL execution plans.
SQL Server does not support function-based indexes as described but it does
offer computed columns that can be used instead. To make use of this,
you have to first add a computed column to the table that can be indexed
afterwards:
SQL Server is able to use this index whenever the indexed expression
appears in the statement. You do not need to rewrite your query to use the
computed column.
27
Chapter 2: The Where Clause
28
User-Defined Functions
User-Defined Functions
There is one important exception. It is, for example, not possible to refer
to the current time in an index definition, neither directly nor indirectly,
as in the following example.
The function GET_AGE uses the current date (SYSDATE) to calculate the age
based on the supplied date of birth. You can use this function in all parts
of an SQL query, for example in select and the where clauses:
The reason behind this limitation is simple. When inserting a new row, the
database calls the function and stores the result in the index and there it
stays, unchanged. There is no periodic process that updates the index. The
database updates the indexed age only when the date of birth is changed
by an update statement. After the next birthday, the age that is stored in
the index will be wrong.
29
Chapter 2: The Where Clause
Caution
PostgreSQL and the Oracle database trust the DETERMINISTIC or
IMMUTABLE declarations— that means they trust the developer.
You can declare the GET_AGE function to be deterministic and use it in
an index definition. Regardless of the declaration, it will not work as
intended because the age stored in the index will not increase as the
years pass; the employees will not get older—at least not in the index.
Other examples for functions that cannot be “indexed” are random number
generators and functions that depend on environment variables.
Think about it
How can you still use an index to optimize a query for all 42-year-
old employees?
30
Over-Indexing
Over-Indexing
A single index cannot support both methods of ignoring the case. We could,
of course, create a second index on LOWER(last_name) for this query, but
that would mean the database has to maintain two indexes for each insert,
update, and delete statement (see also Chapter 8, “Modifying Data”). To
make one index suffice, you should consistently use the same function
throughout your application.
Tip
Unify the access path so that one index can be used by several
queries.
Tip
Always aim to index the original data as that is often the most useful
information you can put into an index.
31
Chapter 2: The Where Clause
Parameterized Queries
There is nothing bad about writing values directly into ad-hoc statements;
there are, however, two good reasons to use bind parameters in programs:
Security
1
Bind variables are the best way to prevent SQL injection .
Performance
Databases with an execution plan cache like SQL Server and the
Oracle database can reuse an execution plan when executing the same
statement multiple times. It saves effort in rebuilding the execution
plan but works only if the SQL statement is exactly the same. If you put
different values into the SQL statement, the database handles it like a
different statement and recreates the execution plan.
When using bind parameters you do not write the actual values but
instead insert placeholders into the SQL statement. That way the
statements do not change when executing them with different values.
1
http://en.wikipedia.org/wiki/SQL_injection
32
Parameterized Queries
Naturally there are exceptions, for example if the affected data volume
depends on the actual values:
99 rows selected.
SELECT first_name, last_name
FROM employees
WHERE subsidiary_id = 20;
---------------------------------------------------------------
|Id | Operation | Name | Rows | Cost |
---------------------------------------------------------------
| 0 | SELECT STATEMENT | | 99 | 70 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 99 | 70 |
|*2 | INDEX RANGE SCAN | EMPLOYEE_PK | 99 | 2 |
---------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("SUBSIDIARY_ID"=20)
An index lookup delivers the best performance for small subsidiaries, but a
TABLE ACCESS FULL can outperform the index for large subsidiaries:
33
Chapter 2: The Where Clause
The subsequent cost calculation will therefore result in two different cost
values. When the optimizer finally selects an execution plan it takes the
plan with the lowest cost value. For the smaller subsidiary, it is the one
using the index.
The cost of the TABLE ACCESS BY INDEX ROWID operation is highly sensitive
to the row count estimate. Selecting ten times as many rows will elevate
the cost value by that factor. The overall cost using the index is then even
higher than a full table scan. The optimizer will therefore select the other
execution plan for the bigger subsidiary.
Tip
Column histograms are most useful if the values are not uniformly
distributed.
For columns with uniform distribution, it is often sufficient to divide
the number of distinct values by the number of rows in the table.
This method also works when using bind parameters.
From this perspective, it is a little bit paradoxical that bind parameters can
improve performance if not using bind parameters enables the optimizer
to always opt for the best execution plan. But the question is at what price?
Generating and evaluating all execution plan variants is a huge effort that
does not pay off if you get the same result in the end anyway.
34
Parameterized Queries
Tip
Not using bind parameters is like recompiling a program every time.
As the developer, you can use bind parameters deliberately to help resolve
this dilemma. That is, you should always use bind parameters except for
values that shall influence the execution plan.
Unevenly distributed status codes like “todo” and “done” are a good
example. The number of “done” entries often exceeds the “todo” records by
an order of magnitude. Using an index only makes sense when searching
for “todo” entries in that case. Partitioning is another example — that is, if
you split tables and indexes across several storage areas. The actual values
can then influence which partitions have to be scanned. The performance
of LIKE queries can suffer from bind parameters as well as we will see in
the next section.
Tip
In all reality, there are only a few cases in which the actual values
affect the execution plan. You should therefore use bind parameters
if in doubt — just to prevent SQL injections.
The following code snippets show how to use bind parameters in various
programming languages.
35
Exploring the Variety of Random
Documents with Different Content
his lips as a steed dashed around the bend, bearing upon his back—
a woman!
Yes, a woman, or, rather a young girl, for she was none other than
Ruth Ramsey, who, quickly discovering an unlooked-for obstacle in
her path, attempted to draw rein. But she was too late; her steed
was a willful animal, not easily checked, and before she could come
to a halt the outlaw leader spurred alongside of her, and his left
hand grasped her bridle rein.
“Leo Randolph! You here!” she demanded.
It was all she could say, and across her face swept a deathly
pallor.
“Yes, sweet Ruth, your lover of lang syne is delighted to behold
you once more,” said the chief, with irony in his voice.
“It was proven you were an outlaw,” she said, “the leader of a wild
and desperate band; men called you Kansas King because you ruled
the border and none dare face you. Yes, all these things were
proven, and—and—I found I had loved unworthily.”
Ruth spoke half aloud, her eyes downcast, as though musing with
the past.
“Ruth, all these things were told against me; what was proven
was that I had been brought up by a fond mother who idolized her
boy, yet upon whose life a stain rested, and hence the curse fell
upon the son. That mother died, Ruth, and then came the news to
her son that a brand rested upon his life.
“Was it any wonder, then, that he threw away the advantages
bestowed upon him by his loving mother, and became a wild and
reckless outcast? Oh, Ruth, you cannot know how I have suffered,
and what a curse, a misery, my life has been. If you knew you would
pity me—and pity begets love—’tis said. You did love me once,
Ruth.”
The outlaw chief laid his hand softly upon the gloved hand of the
girl, who, quietly withdrawing the hand, replied kindly:
“I thought I loved you once, Leo; but I did not know my heart;
and yet, had your life been different, and not a blot upon the earth,
we might have been more to each other than lovers; but you have
not forgotten that when my father exiled you from our home, and I
told you I did not love you, you basely endeavored to carry me off.”
“No, Ruth, I have not forgotten. I loved you, and that must be my
excuse. I longed to have you with me, to have you my bride, and—
forgive me, Ruth—I was mad enough to think that I might persuade
you to become my wife.”
“My consent never could have been won by force, Leo Randolph;
but, this is idle, to thus stand and talk with you. Believe me, I feel
for you in the evil career you have chosen. But I must hasten, for
the night is coming on and I was foolish to venture thus far from the
fort.”
Ruth attempted to ride on, but the outlaw chief still kept his hand
firmly upon her rein while he asked:
“How is it you are thus far from your camp, and alone?”
“I came out with my father and brother for a ride. They discovered
traces of Indians near the fort, and rode on to investigate, telling me
to return, for I was not half a mile away. I lost my road, and only
just now discovered that my way back lay through this gulch.”
Again she urged her horse forward, yet the chief held him firmly in
his strong grasp.
“Mr. Randolph, will you release my bridle rein?” said Ruth, in a
firm voice.
“Miss Ramsey, I will not—hold! Hear me, and heed—you are in my
power, and I am a desperate man. Go with me willingly; become my
wife, and I will relinquish my evil life and live for you alone; refuse,
and——”
“You plead in vain, Mr. Randolph; your evil life has already put out
every spark of regard I ever felt for you. Again I ask you to release
my rein.”
“And again I say I will not. More—if you will not be a willing bride,
you shall be an unwilling one.”
“God have mercy upon me!” groaned poor Ruth as she reeled as if
about to fall from her saddle.
C H A P T E R X X X V.
THE ANSWERED C R Y.
The moonlight that fell weirdly upon the Haunted Valley, and
lighted up the sad scene enacted there, also cast its silvery radiance
upon the mountain hut of the hermit chief. Pacing to and fro in the
moonlight, with quick, nervous tread, was Gray Chief, his brow dark,
and his lips set stern and hard.
A few moments before White Slayer and his chiefs had left a
council which had determined a deadly extermination of every
paleface in the Black Hills. Gray Chief had been pleased with the
decision of White Slayer, for to him all white men were enemies, and
he desired that not only should the miners perish, but also the
outlaws.
In that council it had been decided that they should seem to agree
to Kansas King’s arrangement for an alliance, and by so doing disarm
suspicion, and get him and his men in their power. After that the
Sioux warriors were to fall upon them and not a man should escape
—no, not one, swore the hermit chief.
Having thus disposed of their would-be allies, it was believed that
the Indians could arm themselves with the weapons taken from the
outlaws, and then make war upon the two camps of the invaders.
The old hermit chuckled gleefully as he thought over his plans, and
saw how eagerly the Indians had agreed to them.
Yet, had he known, within the cabin window stood one who had
heard every arrangement made, and after learning all she could,
arose from her crouching attitude and stole away. If the hermit had
known this, he would not have walked the ledge in the moonlight,
gloating over his diabolical invention to rid the Black Hills of every
paleface who had invaded their unknown fastnesses.
After parting with Buffalo Bill, Pearl had returned home and
learned from Valleolo, the Indian woman, that the chiefs were to
assemble at once. Instantly she secreted herself in her room, and
from her ambush learned their plans, after which she hurried away
through the cavern, descended the hills to the Indian village, and
quickly mounted a splendid horse which White Slayer had captured
in battle and presented to her.
Like the wind she then rode through the valleys and over the hills,
directing her course toward the Ramsey settlement, as she dared
not take the lower cañon leading to the fort of the miners. At length
she drew near the spot where she had been told the palefaces were
encamped, and was just turning into the narrow gulch leading to the
stockade fort, when she heard a loud cry for help.
“Help, help! Oh, Heaven, save me!” again rang the cry, and in a
woman’s voice.
With the impulsiveness of her nature, Pearl was about to dash at
once to the rescue, when there came the sound of coming hoofs.
The next instant, riding up the gulch, she beheld two horses bearing
a man and a girl, the man holding the girl firmly in her saddle, and
at the same time grasping with his other hand the bridle rein of her
horse.
They were Kansas King and Ruth Ramsey. Infuriated by her
refusal of his love, the outlaw chief was bearing the girl by force to
his camp, in spite of her heart-rending cries for help.
“Hold!”
The voice was that of a woman, yet it had in it a stern and
determined ring that brought the robber chief and his captive to a
sudden halt. Before them, seated upon her horse, with her rifle
leveled at the broad breast of Kansas King, was Pearl, the Maid of
the Hills. At the command Kansas King drew rein.
“Well, girl, what do you want?” he asked.
“That you ride on and leave that girl alone,” firmly replied Pearl.
“Ha! a stern command from such sweet lips; but what if I refuse?”
“I will kill you.”
“Harsher still, my mountain beauty; but your aim may not be true,
and——”
“One wave of my hand, Kansas King, and you might find out how
true is my aim. Do you think I am a fool, to come this far from my
home unprotected?”
Pearl spoke as though there were a hundred warriors at her back.
The outlaw chief glanced somewhat nervously around, and,
doubtless believing that the rocks and trees did conceal innumerable
redskins, he said:
“You hold the winning card, fair Pearl of the Hills. I yield to the
command of sweet lips, which yet I may punish for their unkind
words with a kiss. Ruth Ramsey, we will meet again. Fair maids, I
bid you good evening.”
Then, with a muttered curse, Kansas King drove his spurs deep
into the flanks of his horse, and dashed away up the gulch at a mad
speed. Before the rattle of his horse’s hoofs died away, there
resounded through the cañon the heavy tramp of many feet. In
dismay, Ruth cried:
“Come; oh, come, for the Indians are coming!”
Pearl listened an instant, and then said:
“No, those are not Indians, for I hear the iron ring against the
rocks of white men’s shod horses; they are your friends.”
Before more could be said a long line of horsemen filed around a
bend in the cañon. Whether friendly or hostile, it was then too late
to fly.
CHAPTER XXXVI.
UNCLE SAM’S BOYS.
The column of horsemen that was filing at a slow trot through the
cañon were, as Pearl had said, not Indians, but palefaces, and with a
half cry of joy, Ruth saw that they were troopers, dressed in the
uniform of United States cavalry. It was a squadron of less than a
score. At their head rode a young and dashing officer of perhaps
twenty-five years of age.
At a glance, womanlike, both the girls took in his superb form,
splendid seat in the saddle, stylish uniform and broad shoulders,
with the straps of a captain thereon. Then they saw his handsome,
daring face, with its dark, earnest eyes, and firm mouth, shaded by
a dark mustache.
Certainly he was an elegant-looking young officer, and into his
frank, noble face the two girls, the daughter of the prairie, and the
child of the hills, gazed with admiration and trust.
With surprise upon his features, a pleased surprise he did not
attempt to conceal, the young officer drew rein before the two girls,
whose horses stood side by side across the cañon, and, respectfully
raising his plumed hat, said pleasantly:
“This is an unlooked-for pleasure—meeting ladies in these wild
hills.”
“And a particular pleasure, sir, to us, at least to me, for there is
certainly need for you and your troopers here,” replied Ruth.
Pearl remained silent, and the young captain again said:
“My instructions were to come into these hills and protect all white
settlers. I expected to find here a band of rude miners—certainly not
any ladies.”
“I, sir, am the daughter of Captain Ramsey,” said Ruth. “He is the
leader of a small party of settlers who came here to establish homes
and also dig for gold; this girl I never met until ten minutes ago,
when she saved me from a terrible fate—a fate to which death was
preferable.”
Ruth Ramsey spoke with exceeding earnestness.
“Indeed!” exclaimed the young officer. “This young lady, then,
does not belong to your settlement. Can there be another band of
settlers in these hills?”
He asked the question with surprise, gazing with admiration upon
Pearl’s lovely face. Pearl flushed slightly, to find herself the object of
such ardent notice, and replied:
“I was on my way to warn the palefaces of danger, when I came
suddenly upon this lady and Kansas King, the outlaw, who was
forcing her to accompany him.”
“Warn the palefaces of danger? Are you not a paleface?” asked
the astonished soldier.
“I am a paleface, yes. But I cannot say more than that I was
going to tell the settlers that White Slayer and his band are to move
to-morrow night upon their forts, and that there is no hope for them
unless they at once leave these hills.”
“And you! Are you not in danger?” said Ruth Ramsey earnestly.
“No, I am not in danger; but you must escape from the red devils,
who will soon be on the warpath against every paleface who has
lately come into the hills.”
“You bring bad news, miss,” said the officer, “and yet I fear true
tidings, as I know the bitterness of the Indians to those who would
settle here. To-morrow night, you say, they will commence the
attack?”
“Yes, sir.”
“And Major Wells will not be up before day after to-morrow,
hasten as he may, and I have but fourteen men with me,” was the
thoughtful statement.
“You have other troops coming, then, sir?” asked Ruth anxiously.
“Yes, over a hundred troopers; I was merely an advance guard;
here, Wentworth, hasten back with all dispatch and ask Major Wells
to ride his horses down but that he reaches here to-morrow night.”
The captain turned to a horseman who was half scout, half soldier,
and a bold-looking fellow, who promptly replied:
“I’ll fetch him, Captain Archer, if hoofs can make it!”
“Do so, Wentworth, and bring him to this point, do you hear?”
“Aye, aye, sir!” and away dashed the courier at full speed.
“Now, young ladies,” said the officer, “there is but one thing for me
to do, and that is to go secretly into camp near here and await the
attack upon the fort, and then endeavor to make the redskins
believe a large force of cavalry has come to the assistance of the
settlers. Were the Indians to know that I had but my present force
they would not fear me, so I beg that you keep my presence in the
hills a secret, and in the time of need I will be on hand. My orders,
Miss Ramsey, are to protect the lives of the settlers.”
“I will guide you to a safe place, sir, where you could conceal a
hundred men,” Pearl volunteered.
Then she considerately added:
“We should first see this lady home.”
“True. Miss Ramsey, we will ride with you to within a short
distance of your camp,” replied the young officer.
The cavalcade at once moved off, Pearl guiding, and as they rode
along the two girls and the young officer chatted pleasantly together.
At length the stockade was visible, and the party halted, while Ruth,
after bidding adieu to the captain, kissed her new-found friend and
rode on alone.
Then away dashed Pearl, side by side with the captain, and behind
came the troopers riding in Indian file. A gallop of two miles brought
them to one of those gorges so common in the Black Hills, and into
this Pearl led the way until they came to a small glen, fertile and well
watered.
“Here you can rest secure, sir. If there is any change in the plans
of the Indians, I will come and let you know,” said she.
Then she made known to the officer all that had transpired, with
which the reader is already acquainted. In surprise and
astonishment, the young man listened: and then said kindly, taking
her hand:
“The settlers have much to thank you for, miss, I assure you, and
it is noble of you to thus warn them of danger, at the risk of your
life, for I feel that you are an inmate of the village of the Sioux to
thus know their plans. This, I hope, will not be our last meeting, and
in full sincerity I say, if in any way I can befriend you, command me.
My name is Edwin Archer, and I am a captain of cavalry, now on the
prairie border.”
Pearl made no reply, waved her hand pleasantly, and away
bounded her steed on the return to the Indian village.
CHAPTER XXXVII.
T H E FA I R Y G L E N .
When Ruth Ramsey returned to the stockade she found the whole
settlement about to turn out in search of her. Her friends were
delighted at her return, for they had believed her lost, or captured
by the Indians, as her father and brother had returned some time
before, and reported that she had started home.
Ruth made known her startling adventure with Kansas King, her
rescue by a strange white girl; but the coming of the cavalry she
kept to herself, as the officer had requested her to do. The settlers
were all in a state of fermentation at the hostile position assumed by
the Sioux, and the coming into the hills of Kansas King and his band.
Buffalo Bill had made known the enmity of the Indians and
advised that the settlers should move over to the miners’ fort until
after the battle they knew must come with the Indians.
There were some who declared against the move, unwilling to
leave off their gold digging, and thus a war of words was
progressing, when suddenly Buffalo Bill again appeared in their
midst, and at once his report settled the matter.
Two hours after, the stockade was deserted by one and all, and
the men at once set off for the miners’ camp, excepting those
designated to go with the women and children into the Haunted
Valley. A mile from the stockade the party divided, with many tears,
kind wishes, and tender farewells, and Buffalo Bill led his precious
charge by the nearest route to the valley where Red Hand awaited
them.
After an hour’s tramp, they entered a narrow gorge, the western
inlet to the valley. Ahead of them Buffalo Bill suddenly descried a
tall, upright form coming toward them.
It was Red Hand. He bowed pleasantly to the party, pressed lightly
the hand Ruth extended to him, and said simply:
“Come.”
Leading the way through the beautiful yet strangely wild glen, Red
Hand turned, after a walk of a third of a mile, into a thick piece of
timber, through which ran an indistinct trail. A still farther walk
through the woods of two hundred yards, and before them arose the
precipitous and lofty sides of the mountain, pierced by several
narrow gorges, that appeared like lanes through the massive hills.
Into one of these chasms, for they were hardly anything more,
Red Hand walked, and soon it widened into a perfect bowl, with
towering walls upon every side. It was a fairy spot, where one would
love to dwell and dream away a lifetime, far away from the cares of
the world.
And there, sheltered against the base of the lofty hills, was a neat
little cabin home—a hermitage in the hills. It was a humble abode,
built of stout logs, and yet around it was an air of comfort, while the
interior, consisting of two rooms, certainly looked cozy and most
comfortable, for the furniture, though of rude manufacture, was
useful, and around the walls were many articles of use and
enjoyment, from rifles, knives, and pistols, cooking utensils, and a
very fair selection of books.
“This was her home,” he said simply and meaningly, speaking to
Buffalo Bill. “From here to his grave is but a short distance, and her
going there has marked a distinct trail. And, friend Cody, last night I
made strange discoveries.”
Turning to Captain Ramsey, Red Hand requested him to keep his
party in the gorge. Promising to bring the anxious mothers, wives,
sisters, and daughters good news, Buffalo Bill set out with Red Hand
for the fort, which they knew, before many hours, would be the
scene of a terrible border battle.
The scout even had his doubts as to a result in favor of the
whites.
“Cody, if it comes to the worst, you can wait in the gorge until the
Indians believe you escaped before the fight, and then make for the
settlement with all haste.”
“I will try to take care of myself,” was the cheerful answer.
“Never mind me, old fellow; but, if we do go under, why, redskins’
scalps will be a drug in the market,” and a sad smile played upon
Red Hand’s face.
CHAPTER XXXVIII.
T H E W A R C R Y.
Night, serenely beautiful, with its silver moon lighting up the bold
scenery upon every hand, came again to the Black Hills, and the
shadow of the mountains fell upon the miners’ fort, where all
seemed lost in deep repose. But the silence resting there was a
treacherous one, for within those stockade walls were half a hundred
brave men resting upon their arms and awaiting the coming of their
foes, who, all knew, were to hurl themselves against them that
night.
Since the day before, when he had left the valley retreat with Red
Hand, Buffalo Bill had been constantly on the move, scouting about
the hills, and his reconnoissance had discovered the plan of attack
decided upon by the Indians.
According to promise, Pearl had met him in the gorge, and told
him that from the ledge she had witnessed the coming of Kansas
King, and heard all that had passed between him and her father,
who had told the outlaw chief that the night following he would
come to his camp with five hundred warriors, and that they would
together move on the miners’ stronghold.
Kansas King had agreed to Gray Chief’s plans, and then took his
departure, apparently satisfied with the good faith of his allies. As
for the old hermit, he laughed in his sleeve at the way he had fooled
the outlaw, for it was his intention that very night to hurl his whole
force upon the robber camp, and, after a general massacre, to divide
his warriors into two parties and at once attack the two paleface
encampments.
As soon as he learned the plans of the Indians, and also heard
from Pearl about the arrival of the cavalry in the Black Hills, Buffalo
Bill at once set out on his return to the stronghold.
Whether Kansas King suspected the hermit chief of bad faith, or
determined to strike a blow himself against the settlements, is not
known; but certain it is, that, as soon as darkness set in, he moved
his men at once toward the Ramsey stockade, and after a gallant
charge up to the walls, discovered that the occupants had deserted
the place.
Chagrined at this discovery, the outlaw chief rode with all dispatch
toward the stronghold of the miners, and arrived there about the
time that Gray Chief and his red warriors reached the camping
ground of the robbers, to find that they had fled.
With rage at the move of Kansas King, the Indians at once set out
for the Ramsey settlement, gloating over their anticipated revel in
blood. Again were they doomed to disappointment, and in fear that
their enemies had escaped them they rode rapidly for the stronghold
of the miners.
Before they arrived, however, they heard the rattle of firearms.
Then it flashed across the hermit chief that Kansas King had
outwitted him and was determined to alone take the plunder from
the miners and reduce their stronghold to ashes.
The firing grew louder, and then the fort came in sight, the flashes
of the rifles lighting up the dark mountainside. As the band of
warriors pressed on, Kansas King suddenly confronted the hermit
chief, and, with coolness, said:
“Well, old man, you procrastinated too much, so I have begun the
fight!”
Both men felt that the other was playing some deep game; yet
they were anxious to receive aid, the one from the other. The
outlaws had already suffered severely, and at a glance the hermit
chief and White Slayer felt that the stronghold would not be easily
taken.
So the outlaws and the Sioux concluded to fight together against
the miners. The Indians were thrown into position, and the battle at
once raged in all its fierceness. In vain the outlaws, under their
reckless young leader, hurled themselves against the stockade walls;
in vain the warriors resorted to every cunning artifice known to
them.
The brave little garrison poured in constantly a galling fire upon
their enemies, and many an outlaw and Indian bit the dust.
“Come, this will never do. We must charge in column with our
whole force and throw ourselves over the walls. I will lead,” cried
Kansas King, almost wild with fury at the stubborn resistance of the
gallant defenders.
“It is the only chance, I see. Here, White Slayer, form your men
for a bold rush,” replied the stern old hermit chief.
Then, with demoniacal yells, the mad column of outlaws and
redskins started upon the charge. Like hail the leaden bullets fell in
their midst, and terrible was the havoc; but on they pressed—Kansas
King, the hermit chief, and White Slayer at their head.
On, still on, until the dark column reached the stockade. Springing
upon the shoulders of the braves, the daring White Slayer was the
next instant upon the top of the wall, his wild war whoop echoing
defiance and triumph.
But suddenly behind the Indians came a ringing order in trumpet
tones:
“Troopers to the rescue—charge!”
Then was heard the hearty cheer of regular soldiers, a rattling of
sabers, a heavy tramping of many hoofs, and upon the rear of the
attacking force rushed a squadron of cavalry, half a hundred strong,
and at their head rode Captain Edwin Archer.
The sight that followed was a scene of terrible carnage, for in wild
dismay the Indians and outlaws fled, the battle lost to them at the
moment they believed victory their own. As the stampede became
general, two men mounted their horses and dashed rapidly away up
the gorge.
But upon their tracks rode two other men who had dashed out of
the stronghold in hot pursuit. The two who were flying in advance
for their lives were the hermit chief and Kansas King, both bitterly
cursing their misfortune.
The two men who had ridden from the stronghold in pursuit were
Red Hand and Buffalo Bill. On flew the two chiefs up the dark gorge,
and like bloodhounds on the trail rode Red Hand and the famous
scout.
Up the valley, over the ridges, through the cañon, up to the base
of the hill, whereon stood the hermit’s cabin, rushed the riders. Here
the two fugitives sprang from their horses and darted up the steep
ascent.
But close behind them was Red Hand and Buffalo Bill. At last the
ledge was reached, and upon it the hermit turned at bay, for he saw
that Red Hand was close behind him. Like an enraged beast, the
hermit chief cried:
“Tracked to my lair at last—at last; but, Vincent Vernon, you shall
die!”
With gleaming knife, the old hermit sprang forward, but Red
Hand, with a cry of rage, as though he recognized the man before
him, and had some bitter injury of the past to avenge, met him with
a terrible earnestness—met him to hurl him back from him with a
strength that was marvelous, and with one plunge of his blade sent
its keen point deep into the broad bosom of his foe.
One stifled cry, and the hermit chief fell back his full length upon
the hard rock, just as Kansas King, who had found the door of the
cabin barred against him, turned also at bay, to be met by a blow
from the pistol butt of Buffalo Bill, which felled him, stunned, to the
earth.
CHAPTER XXXIX.
THE MYSTERY S O LV E D.
But that was long ago, and time had brought many changes, and
branded his once proud name with infamy. Fully six feet in height,
and of supple, graceful form, the chief of the Branded Brotherhood
wore buckskin, with trousers elaborately worked with beads, and
fringed down the outer seams.
Instead of moccasins, his feet were incased in high-top cavalry
boots, armed with huge spurs; and a blue silk shirt and Mexican
jacket, profusely adorned with silver buttons, completed his
costume, excepting a gray slouch hat, with exceedingly broad brim,
which was turned up on one side.
The hands and face of the outlaw were burned as brown as the
sun and exposure could make them; a heavy brown beard, of a like
shade, with his long, curling hair, completely hid the lower features
of his face; but his nose was straight and firm, his forehead broad
and intellectual, his eyes strangely fiery and savage, while within
their inmost depths was an expression hard to fathom, for at times it
looked like fear, again was expressive of sadness, and at others of
hatred and mischief.
His men knew him only as “the chief.” Along the frontier he was
called “Captain Ricardo, the Bandit,” but what his real name was
none knew.
Nor did any one know whence he came, only it was surmised that
he had once been a distinguished cavalry officer, who, having been
dismissed from the service for a crime committed, had taken to the
plains as a highway robber, until, in a few years he had organized
the band of which he was chief, and which had spread terror far and
wide along the border.
The chief’s horse, a splendid-looking iron-gray, fed near by, and,
serving as a resting place for his arm was a Mexican saddle, with a
belt, containing two revolvers and a bowie knife, which Captain
Ricardo kept near at hand.
The persons immediately surrounding the chief consisted of the
negro cook, a cunning-faced, wiry fellow, black as a coal, who never,
sleeping or waking, went without his revolver and knife, which he
kept in a large leather belt around his waist.
It was said the negro, whom his master called Buttermilk—as a
contrast to his color—knew more of the chief’s life than did any one
else; but, if so, he was never known to betray that knowledge.
Then there was an Indian scout, a powerful and evil-looking Sioux,
who had betrayed his own people and then sought refuge in the
outlaw band, and, thoroughly knowing the whole country, Captain
Ricardo found him an able ally.
There were also two others, both white men; one a square-
framed, brutal-faced man of forty-five, whom the chief had made his
second in command, and the other a renegade trapper and hunter,
who, having robbed his comrades, a few years before, had sought
the band for protection.
Turning to his officer, who was impatiently watching the rather
lazy preparations of the negro, Buttermilk, Captain Ricardo
remarked, in a voice strangely soft and pleasant for one who led his
wild life:
“I see no reason why the train should not fall easily into our
hands, for they must cross the river at a point near here.”
“Yes, chief; but if we wait for them to come up here the troop will
have rejoined them, and now, you know, the Injun here says
Captain la Clyde and his troopers are off on a scout and the train has
only its own men to guard it.”
This was the answer of the lieutenant, who answered to the name
of Red Roark, both on account of his red hair and beard and his
bloody deeds, for at heart he was a perfect brute.
“The chief’s right,” said the renegade trapper. “You hear me talk,
Red Roark. If we waits for them fellers here they’ll come
onsuspectinglike, right onto our trap; but ef we goes out on the
prairie to fight ’em, then we’ll get some hard knocks and no pay. You
see, I’s been in thar train, as I told the chief, and I knows what I’s
talkin’ about.”
The trapper was squatted down on the ground near the chief, who
replied:
“You really went into their train, Long Dave?”
“You bet! I just tole ’em I was a hunter as was going to the forts,
and I tell you they has just got a ticklish-lookin’ set of fellers to
tackle. They axed me ’bout you, chief, and ef I thought they’d run
across you, and, of course, I tole ’em no, and they said ef they did
you’d have to git up early to catch them napping.”
“How many fighting men are there, Long Dave?”
“Some forty or more, big boys included; and then there’s the
twenty troopers under Captain la Clyde, who you might count on, for
he just goes scouting around, you see, and has taken a shine to one
of the gals in the train, and he’s going to be on hand when it comes
to a row, you bet.”
“Which way did the cavalry go when they left the train last night?”
“That’s jist what I was going to find out when I seed that devil of
a fellow they call Buffalo Bill a-coming across the prairie, and I jest
lit out for these diggin’s, you bet, chief, kase I knows that fellow, and
don’t want him near me.”
“You refer to Buffalo Bill, the army scout?”
“Yes, the fellow is getting mighty bold of late.”
“He is, indeed, and I would be willing to pay a round sum to take
him, for he has thwarted my plans more than once. Well, we’ll lie in
wait for the train here, and to-night, Long Dave, you and Black Wolf
must start out and bring me the exact whereabouts of both the train
and the troopers, for this rich harvest must not be lost for want of
reaping. Now let us have supper, Buttermilk, you lazy dog.”
“You be lazy, too, if you have to cook tough ole buffalo bull a
t’ousand year ole,” grumbled the negro, who always had a way of
answering back when addressed, and which his master appeared not
to notice, but would severely punish in any one else.
Just as night set in the chief and his three comrades fell to and
were soon enjoying the really delicious meal which Buttermilk had
prepared. An hour or more passed away and the bandit camp was as
silent as a “city of the dead,” for the men had rolled themselves in
their blankets and sought their rest, excepting the half a dozen
sentinels who had been set to keep watch and ward.
Now and then the howl of a hungry wolf out on the prairie broke
the stillness of the night, or the startled snort of a horse was heard.
Then again all was quiet, until suddenly there rang forth the sharp
crack of a rifle, followed by a death shriek. Instantly every man in