Oracle Database Transactions and Locking Revealed: Building High Performance Through Concurrency
By Darl Kuhn and Thomas Kyte
()
About this ebook
Good transaction design is an important facet of highly-concurrent applications that are run by hundreds, even thousands, of users who are executing transactions at the same time. Transaction design, in turn, relies on a good understanding of how the database engine manages the locking of resources to prevent access conflicts and data loss that might otherwise result from concurrent access to data in the database. This book provides a solid and accurate explanation of how locking and concurrency are dealt with by Oracle Database. You will learn how the Oracle Database architecture accommodates user transactions, and how you can write code to mesh with the way in which Oracle Database is designed to operate.
Oracle Database Transactions and Locking Revealed covers in detail the various lock types, and also different locking schemes such as pessimistic and optimistic locking. Then you will learn about transaction isolation and multi-version concurrency, and how the various lock types support Oracle Database’s transactional features. You will learn tips for transaction design, as well as some bad practices and habits to avoid. Coverage is also given to redo and undo, and their role in concurrency. The book is loaded with insightful code examples that drive home each concept. This is an important book that anyone developing highly-concurrent applications will want to have handy on their shelf.
What You Will Learn
- Avoid application lockups due to conflicts over accessing the same resource
- Understand how Oracle prevents one application from overwriting another’s modifications
- Create transaction designs that mesh with how Oracle Database is designed
- Build high-throughput applications supporting thousands of concurrent users
- Design applications to take full advantage of Oracle’s powerful database engine
- Gain a fundamental knowledge of Oracle’s transaction and locking architecture
- Develop techniques to quickly diagnose and resolve common locking issues
Who This Book Is For
Oracle developers and database administrators faced with troubleshooting and solving deadlocks, locking contention, and similar problems that are encountered in high-concurrency environments; and application developers wanting to design their applications to excel at multi-user concurrency by taking full advantage of Oracle Database’s multi-versioning and concurrency support
Read more from Darl Kuhn
Expert Oracle Database Architecture: Techniques and Solutions for High Performance and Productivity Rating: 0 out of 5 stars0 ratingsExpert Oracle Database Architecture Rating: 0 out of 5 stars0 ratingsPro Oracle Database 18c Administration: Manage and Safeguard Your Organization’s Data Rating: 0 out of 5 stars0 ratings
Related to Oracle Database Transactions and Locking Revealed
Related ebooks
Spark: Big Data Cluster Computing in Production Rating: 0 out of 5 stars0 ratingsThe Modern Data Warehouse in Azure: Building with Speed and Agility on Microsoft’s Cloud Platform Rating: 0 out of 5 stars0 ratingsInstant Oracle GoldenGate Rating: 0 out of 5 stars0 ratingsSQLite Database Programming for Xamarin: Cross-platform C# database development for iOS and Android using SQLite.XM Rating: 0 out of 5 stars0 ratingsPolyBase Revealed: Data Virtualization with SQL Server, Hadoop, Apache Spark, and Beyond Rating: 0 out of 5 stars0 ratingsOracle Database XE 11gR2 Jump Start Guide Rating: 0 out of 5 stars0 ratingsIntroducing .NET for Apache Spark: Distributed Processing for Massive Datasets Rating: 0 out of 5 stars0 ratingsPractical Oracle Cloud Infrastructure: Infrastructure as a Service, Autonomous Database, Managed Kubernetes, and Serverless Rating: 0 out of 5 stars0 ratingsDocker High Performance Rating: 0 out of 5 stars0 ratingsPractical Entity Framework: Database Access for Enterprise Applications Rating: 0 out of 5 stars0 ratingsPro Ember Data: Getting Ember Data to Work with Your API Rating: 0 out of 5 stars0 ratingsGeneric Pipelines Using Docker: The DevOps Guide to Building Reusable, Platform Agnostic CI/CD Frameworks Rating: 0 out of 5 stars0 ratingsReact Components Rating: 0 out of 5 stars0 ratingsUnderstanding Oracle APEX 20 Application Development: Think Like an Application Express Developer Rating: 0 out of 5 stars0 ratingsGetting Started with Haskell Data Analysis: Put your data analysis techniques to work and generate publication-ready visualizations Rating: 0 out of 5 stars0 ratingsGetting Started with CockroachDB: A guide to using a modern, cloud-native, and distributed SQL database for your data-intensive apps Rating: 0 out of 5 stars0 ratingsOracle SQL In 10 Minutes Rating: 5 out of 5 stars5/5Sql : The Ultimate Beginner to Advanced Guide To Master SQL Quickly with Step-by-Step Practical Examples Rating: 0 out of 5 stars0 ratingsAdvanced ASP.NET Core 3 Security: Understanding Hacks, Attacks, and Vulnerabilities to Secure Your Website Rating: 0 out of 5 stars0 ratingsPractical Oracle SQL: Mastering the Full Power of Oracle Database Rating: 0 out of 5 stars0 ratingsDeveloping Web Components with TypeScript: Native Web Development Using Thin Libraries Rating: 0 out of 5 stars0 ratingsJava: Tips and Tricks to Programming Code with Java: Java Computer Programming, #2 Rating: 0 out of 5 stars0 ratingsJava: Tips and Tricks to Programming Code with Java Rating: 0 out of 5 stars0 ratingsHands-On Deep Learning for Images with TensorFlow: Build intelligent computer vision applications using TensorFlow and Keras Rating: 0 out of 5 stars0 ratingsWebpack for Beginners: Your Step-by-Step Guide to Learning Webpack 4 Rating: 0 out of 5 stars0 ratingsRefactoring Legacy T-SQL for Improved Performance: Modern Practices for SQL Server Applications Rating: 0 out of 5 stars0 ratingsBeginning DevOps with Docker: Automate the deployment of your environment with the power of the Docker toolchain Rating: 0 out of 5 stars0 ratings
Databases For You
Oracle DBA Mentor: Succeeding as an Oracle Database Administrator Rating: 0 out of 5 stars0 ratingsBlockchain Basics: A Non-Technical Introduction in 25 Steps Rating: 5 out of 5 stars5/5SQL QuickStart Guide: The Simplified Beginner's Guide to Managing, Analyzing, and Manipulating Data With SQL Rating: 4 out of 5 stars4/5Beginning Microsoft Power BI: A Practical Guide to Self-Service Data Analytics Rating: 0 out of 5 stars0 ratingsAccess 2019 For Dummies Rating: 0 out of 5 stars0 ratingsGrokking Algorithms: An illustrated guide for programmers and other curious people Rating: 4 out of 5 stars4/5COMPUTER SCIENCE FOR ROOKIES Rating: 0 out of 5 stars0 ratingsPractical Data Analysis Rating: 4 out of 5 stars4/5CompTIA DataSys+ Study Guide: Exam DS0-001 Rating: 0 out of 5 stars0 ratingsBehind Every Good Decision: How Anyone Can Use Business Analytics to Turn Data into Profitable Insight Rating: 5 out of 5 stars5/5Python Projects for Everyone Rating: 0 out of 5 stars0 ratingsLearn SQL in 24 Hours Rating: 5 out of 5 stars5/5Access 2016 For Dummies Rating: 0 out of 5 stars0 ratingsGo in Action Rating: 5 out of 5 stars5/5The Analytic Detective: Decipher Your Company’s Data Clues and Become Irreplaceable Rating: 0 out of 5 stars0 ratingsAccess for Beginners: Access Essentials, #1 Rating: 0 out of 5 stars0 ratingsLearn SQL Server Administration in a Month of Lunches Rating: 3 out of 5 stars3/5Learning Oracle 12c: A PL/SQL Approach Rating: 0 out of 5 stars0 ratingsAccess 2010 All-in-One For Dummies Rating: 4 out of 5 stars4/5Learn Git in a Month of Lunches Rating: 0 out of 5 stars0 ratingsAzure SQL Revealed: A Guide to the Cloud for SQL Server Professionals Rating: 0 out of 5 stars0 ratingsA Concise Guide to Object Orientated Programming Rating: 0 out of 5 stars0 ratingsGetting Started with SQL Server 2014 Administration Rating: 0 out of 5 stars0 ratingsPython and SQLite Development Rating: 0 out of 5 stars0 ratingsPractical SQL Rating: 4 out of 5 stars4/5SQL in 30 Pages Rating: 4 out of 5 stars4/5Learning PostgreSQL Rating: 1 out of 5 stars1/5
Reviews for Oracle Database Transactions and Locking Revealed
0 ratings0 reviews
Book preview
Oracle Database Transactions and Locking Revealed - Darl Kuhn
© Darl Kuhn and Thomas Kyte 2021
D. Kuhn, T. KyteOracle Database Transactions and Locking Revealedhttps://doi.org/10.1007/978-1-4842-6425-6_1
1. Getting Started
Darl Kuhn¹ and Thomas Kyte²
(1)
Morrison, CO, USA
(2)
Denver, CO, USA
I spend a great deal of time working with Oracle technology. I’m often called in to assist with diagnosing and resolving performance issues. Many of the applications I’ve worked with have experienced problems in part due to the developers (and to some degree database administrators) treating the database as if it was a black box. In other words, the team hadn’t spent any time becoming familiar with the database technology that was at the core of their application. In this regard, a fundamental piece of advice I have is do not treat the database as a nebulous piece of software to which you simply feed queries and receive results. The database is the most critical piece of most applications. Trying to ignore its internal workings and database vendor–specific features results in architectural decisions from which high performance cannot be achieved.
Having said that, at the core of understanding how a database works is a solid comprehension of how its transactional control mechanisms are implemented. The key to gaining maximum utility from an Oracle database is based on understanding how Oracle concurrently manages transactions while simultaneously providing consistent point-in-time results to queries. This knowledge forms the foundation from which you can make intelligent decisions resulting in highly concurrent and well-performing applications. Also important is that every database vendor implements transaction and concurrency control features differently. If you don’t recognize this, your database will give wrong
answers, and you will have large contention issues, leading to poor performance and limited scalability.
Background
There are several topics underpinning how Oracle handles concurrent access to data. I’ve divided these into the following categories:
Locking
Concurrency Control
Multiversioning
Transactions
Redo and Undo
These features are the focus of this book. Since these concepts are all interrelated, it’s difficult to pick which topic to discuss first. For example, in order to discuss locking, you must understand what a transaction is, and vice versa. Understanding these topics is a prerequisite for being able to successfully implement scalable and robust applications utilizing Oracle databases. The order in which these are introduced here in this chapter is also the order these topics are covered in subsequent chapters in the book.
Locking
The database uses locks to ensure that, at most, one transaction is modifying a given piece of data at any given time. Basically, locks are the mechanism that allows for concurrency—without some locking model to prevent concurrent updates to the same row, for example, multiuser access would not be possible in a database. However, if overused or used improperly, locks can inhibit concurrency. If you or the database itself locks data unnecessarily, fewer people will be able to concurrently perform operations. Thus, understanding what locking is and how it works in your database is vital if you are to develop a scalable, correct application.
What is also vital is that you understand that each database implements locking differently. Some have page-level locking, others row-level; some implementations escalate locks from row level to page level, some do not; some use read locks, others don’t; some implement serializable transactions via locking and others via read-consistent views of data (no locks). These small differences can balloon into huge performance issues or downright bugs in your application if you don’t understand how they work.
The following points sum up Oracle’s locking policy:
Oracle locks data at the row level on modification. There is no lock escalation to a block or table level.
Oracle never locks data just to read it. There are no locks placed on rows of data by simple reads.
A writer of data does not block a reader of data. Let me repeat: reads are not blocked by writes. This is fundamentally different from many other databases, where reads are blocked by writes. While this sounds like an extremely positive attribute (and it generally is), if you do not understand this thoroughly and you attempt to enforce integrity constraints in your application via application logic, you are most likely doing it incorrectly.
A writer of data is blocked only when another writer of data has already locked the row it was going after. A reader of data never blocks a writer of data.
You must take these facts into consideration when developing your application and you must also realize that this policy is unique to Oracle; every database has subtle differences in its approach to locking. Even if you go with lowest common denominator SQL in your applications, the locking and concurrency control models employed by each vendor assure something will be different. A developer who does not understand how his or her database handles concurrency will certainly encounter data integrity issues. This is particularly common when a developer moves from another database to Oracle, or vice versa, and neglects to take the differing concurrency mechanisms into account in the application. Chapters 2 and 3 in this book will provide you an in-depth technical discussion of locking.
Concurrency Control
Concurrency control ensures that no two transactions modify the same piece of data at the same time. This is an area where databases differentiate themselves. Concurrency control is an area that sets a database apart from a file system and databases apart from each other. As a programmer, it is vital that your database application works correctly under concurrent access conditions, and yet time and time again, this is something people fail to test. Techniques that work well if everything happens consecutively do not necessarily work so well when everyone does them simultaneously. If you don’t have a good grasp of how your particular database implements concurrency control mechanisms, then you will
Corrupt the integrity of your data
Have applications run slower than they should with a small number of users
Decrease your applications’ ability to scale to a large number of users and transactions
Notice I don’t say, you might…
or you run the risk of…,
but rather that invariably you will do these things. You will do these things without even realizing it. Without correct concurrency control, you will corrupt the integrity of your database because something that works in isolation will not work as you expect in a multiuser situation. Your application will run slower than it should because you’ll end up waiting for data. Your application will lose its ability to scale because of locking and contention issues. As the queues to access a resource get longer, the wait gets longer and longer.
An analogy here would be a backup at a tollbooth. If cars arrive in an orderly, predictable fashion, one after the other, there won’t ever be a backup. If many cars arrive simultaneously, queues start to form. Furthermore, the waiting time does not increase linearly with the number of cars at the booth. After a certain point, considerable additional time is spent managing
the people who are waiting in line, as well as servicing them (the parallel in the database would be context switching).
Concurrency issues are the hardest to track down; the problem is similar to debugging a multithreaded program. The program may work fine in the controlled, artificial environment of the debugger, but it crashes horribly in the real world. For example, under race conditions, you find that two threads can end up modifying the same data structure simultaneously. These kinds of bugs are extremely difficult to track down and fix. If you only test your application in isolation and then deploy it to dozens of concurrent users, you are likely to be (painfully) exposed to an undetected concurrency issue.
So, if you are used to the way other databases work with respect to query consistency and concurrency, or you never had to grapple with such concepts (i.e., you have no real database experience), you can now see how understanding how this works will be important to you. In order to maximize Oracle’s potential, and to implement correct code, you need to understand these issues as they pertain to Oracle—not how they are implemented in other databases. Oracle concurrency control internals are covered in detail in Chapter 4.
Multiversioning
Multiversioning is related to concurrency control, as it forms the foundation for Oracle’s concurrency control mechanism. Oracle operates a multiversion, read-consistent concurrency model. In Chapter 4, we’ll cover the technical aspects in more detail, but, essentially, it is the mechanism by which Oracle provides for the following:
Read-consistent queries: Queries that produce consistent results with respect to a point in time.
Nonblocking queries: Queries are never blocked by writers of data, as they are in other databases.
These are two especially important concepts in the Oracle database. The term multiversioning basically describes Oracle’s ability to simultaneously maintain multiple versions of the data in the database. The term read consistency reflects the fact that a query in Oracle will return results from a consistent point in time. Every block used by a query will be as of
the same exact point in time—even if it was modified or locked while you performed your query.
If you understand how multiversioning and read consistency work together, you will always understand the answers you get from the database. Before we explore in a little more detail how Oracle does this, we’ll demonstrate how Oracle implements multiversioning. Table 1-1 contains the timing and operations of this demonstration.
Table 1-1
Demonstrating Multiversioning in Oracle
Note
In the following examples, you may not get the exact results as shown here. That’s to be expected depending on what version of the database you have installed and what features and components are enabled.
In the following code, we first connect to the root container as SYS and perform the operations described in Table 1-1:
sqlplus / as sysdba
SQL> -- Time 1
SQL> create table t as select username from all_users where username like 'SYS%';
Table created.
SQL> -- Time 2
SQL> set autoprint off
SQL> variable x refcursor;
SQL> begin
open :x for select * from t;
end;
/
PL/SQL procedure successfully completed.
SQL> -- Time 3
SQL> declare
pragma autonomous_transaction;
-- you could do this in another
-- sqlplus session as well, the
-- effect would be identical
begin
delete from t;
commit;
end;
/
PL/SQL procedure successfully completed.
SQL> -- Time 4
SQL> select * from t;
no rows selected
SQL -- Time 5
SQL> print x
USERNAME
---------------
SYS
SYSTEM
SYSBACKUP
SYSDG
SYSKM
SYSRAC
SYS$UMF
SYSMAN
8 rows selected.
At time 1, we created a test table, T, and loaded it with some data from the ALL_USERS table. At time 2, we opened a cursor on that table. We fetched no data from that cursor, we just opened the cursor and kept it open.
Note
Bear in mind that Oracle does not pre-answer
the query. It does not copy the data anywhere when you open a cursor—imagine how long it would take to open a cursor on a one-billion-row table if it did. The cursor opens instantly and it answers the query as it goes along. In other words, the cursor just reads data from the table as you fetch from it.
At time 3, the same session (or maybe another session would do this; it would work as well), we proceed to delete all data from the table. We even go as far as to COMMIT work on that delete action.
After committing, at time 4 we select from table T and the rows are gone—but are they? In fact, they are retrievable via the cursor (or via a FLASHBACK query using the AS OF clause). The fact is that the resultset returned to us at time 5 by the PRINT command was preordained at the point in time we opened the cursor. We had not touched a single block of data in that table during the open, but the answer was already fixed in stone. We have no way of knowing what the answer will be until we fetch the data; however, the result is immutable from our cursor’s perspective. It is not that Oracle copied all of the preceding data to some other location when we opened the cursor; it was actually the DELETE command that preserved our data for us by placing it (the before image copies of rows as they existed before the DELETE) into a data area called an undo or rollback segment (more on this shortly).
Transactions
A transaction comprises a unit of database work. Transactions are a core feature of database technology. They are part of what distinguishes a database from a file system. And yet, they are often misunderstood and many developers do not even know that they are accidentally not using them.
Transactions take the database from one consistent state to the next consistent state. When you issue a COMMIT, you are assured that all your changes have been successfully saved and that any data integrity checks and rules have been validated. Oracle’s transactional control architecture ensures that consistent data is provided every time, under highly concurrent data access conditions. Transactions are the focus of Chapter 5.
Redo and Undo
Key to Oracle’s durability (recovery) mechanism is redo, and core to multiversioning (read consistency) is undo. Oracle uses redo to capture how the transaction changed the data; this allows you to replay the transaction (in the event of an instance crash or a media failure). Oracle uses undo to store the before image of a modified block; this allows you to reverse or roll back a transaction. Undo also is a key part of implementing read consistency. Recall previously in the chapter when we demonstrated how Oracle implements multiversioning. Figure 1-1 shows how read consistency is implemented through accessing undo.
../images/332165_2_En_1_Chapter/332165_2_En_1_Fig1_HTML.jpgFigure 1-1
Implementing read consistency via undo
It can be said that developers do not need to understand the details of redo and undo as much as DBAs, but developers do need to know the role they play in the database. It’s vital to understand how redo and undo are related to a COMMIT or ROLLBACK statement . It’s also important to understand that generating redo and undo consumes database resources, and it’s essential to be able to measure and manage that resource consumption. Chapters 6 and 7 do a deep dive into redo and undo internals.
Summary
In the following chapters, we’ll discover that different databases have different ways of doing things. What works well in SQL Server may not work as well in Oracle and vice versa. We’ll also see that understanding how Oracle implements locking, concurrency control, and transactions is absolutely vital to the success of your application. This book first discusses Oracle’s basic approach to these issues, the types of locks that can be applied (DML, DDL, and latches), and the problems that can arise if locking is not implemented carefully (deadlocking, blocking, and escalation).
We’ll also explore my favorite Oracle feature, multiversioning, and how it affects concurrency controls and the very design of an application. Here we will see that all databases are not created equal and that their very implementation can have an impact on the design of our applications. We’ll start by reviewing the various transaction isolation levels as defined by the ANSI SQL standard and see how they map to the Oracle implementation (as well as how the other databases map to this standard). Then we’ll look at what implications multiversioning, the feature that allows Oracle to provide nonblocking reads in the database, might have for us.
This book also examines how transactions should be used in Oracle and exposes some bad habits that may have been picked up when developing with other databases. In particular, we look at the implications of atomicity and how it affects statements in Oracle. We also discuss transaction control statements (COMMIT, SAVEPOINT, and ROLLBACK), integrity constraints, distributed transactions (the two-phase commit, or 2PC), and autonomous transactions.
The last few chapters of this book delve into redo and undo. After first defining redo, we examine what exactly a COMMIT does. We discuss how to find out how much redo is being generated and how to significantly reduce the amount of redo generated for certain operations using the NOLOGGING clause. We also investigate redo generation in relation to issues such as block cleanout and log contention. In the undo section of the chapter, we examine the role of undo data and the operations that generate the most/least undo. Finally, we’ll look at common techniques to troubleshoot issues such as the infamous ORA-01555: snapshot too old error, blocking, and locking sessions, possible causes of these issues, and how to avoid associated problems.
© Darl Kuhn and Thomas Kyte 2021
D. Kuhn, T. KyteOracle Database Transactions and Locking Revealedhttps://doi.org/10.1007/978-1-4842-6425-6_2
2. Locking and Blocking
Darl Kuhn¹ and Thomas Kyte²
(1)
Morrison, CO, USA
(2)
Denver, CO, USA
One of the key challenges in developing multiuser, database-driven applications is to maximize concurrent access and, at the same time, ensure that each user is able to read and modify the data in a consistent fashion. The locking mechanisms that allow this to happen are key features of any database, and Oracle excels in providing them. However, Oracle’s implementation of these features is specific to Oracle—just as SQL Server’s implementation is to SQL Server—and it is up to you, the application developer, to ensure that when your application performs data manipulation, it uses these mechanisms correctly. If you fail to do so, your application will behave in an unexpected way, and inevitably the integrity of your data will be compromised.
Note
Ensure that you review the material in the Setting Up Your Environment
section of the Introduction
of this book. It contains setup information that lays the foundation for the code used in many examples in this book such as the SCOTT and YODA schemas.
What Are Locks?
Locks are mechanisms used to regulate concurrent access to a shared resource. Note how I used the term shared resource
and not database row.
It is true that Oracle locks table data at the row level, but it also uses locks at many other levels to provide concurrent access to various resources. For example, while a stored procedure is executing, the procedure itself is locked in a mode that allows others to execute it, but it will not permit another user to alter that instance of that stored procedure in any way. Locks are used in the database to permit concurrent access to these shared resources while at the same time providing data integrity and consistency.
In a single-user database, locks are not necessary. There is, by definition, only one user modifying the information. However, when multiple users are accessing and modifying data or data structures, it is crucial to have a mechanism in place to prevent concurrent modification of the same piece of information. This is what locking is all about.
It is very important to understand that there are as many ways to implement locking in a database as there are databases. Just because you have experience with the locking model of one particular relational database management system (RDBMS) does not mean you know everything about locking. For example, before I got heavily involved with Oracle, I used other databases including Sybase, Microsoft SQL Server, and Informix. All three of these databases provide locking mechanisms for concurrency control, but there are deep and fundamental differences in the way locking is implemented in each one.
To demonstrate this, I’ll outline my progression from a Sybase SQL Server developer to an Informix user and finally to an Oracle developer. This happened many years ago, and the SQL Server fans out there will tell me But we have row-level locking now!
It is true: SQL Server may now use row-level locking, but the way it is implemented is totally different from the way it is done in Oracle. It is a comparison between apples and oranges, and that is the key point.
As a SQL Server programmer (many years ago), I would hardly ever consider the possibility of multiple users inserting data into a table concurrently. It was something that just didn’t often happen in that database. At that time, SQL Server provided only for page-level locking, and since all the data tended to be inserted into the last page of nonclustered tables, concurrent inserts by two users was simply not going to happen.
Note
A SQL Server clustered table (a table that has a clustered index) is in some regard similar to, but very different from, an Oracle cluster. SQL Server used to only support page (block)-level locking; if every row inserted was to go to the end
of the table, you would never have had concurrent inserts or concurrent transactions in that database. The clustered index in SQL Server was used to insert rows all over the table, in sorted order by the cluster key, and as such improved concurrency in that database.
Exactly the same issue affected concurrent updates (since an UPDATE was really a DELETE followed by an INSERT in SQL Server). Perhaps this is why SQL Server, by default, commits or rolls back immediately after execution of each statement, compromising transactional integrity in an attempt to gain higher concurrency.
So in most cases, with page-level locking, multiple users could not simultaneously modify the same table. Compounding this was the fact that while a table modification was in progress, many queries were also effectively blocked against that table. If I tried to query a table and needed a page that was locked by an update, I waited (and waited and waited). The locking mechanism was so poor that providing support for transactions that took more than a second was deadly—the entire database would appear to freeze. I learned a lot of bad habits as a result. I learned that transactions were bad
and that you ought to commit rapidly and never hold locks on data. Concurrency came at the expense of consistency. You either wanted to get it right or get it fast. I came to believe that you couldn’t have