Lab: Concurrency Control

In this lab, you will experiment with PostgreSQL’s concurrency control mechanisms.

You should work on this assignment either individually or with a partner.

Instructions

These exercises will use a database consisting of a single table which maintains very minimal information about bank accounts:

account(number, balance)

where number is an integer and is the primary key, and balance is a decimal number representing a dollar amount.

As you complete the steps below, be careful to follow the directions exactly.

Step 0: Set Up PostgreSQL

Run the following command to start a container running a PostgreSQL server.

docker run --name concurrency-lab --detach --env POSTGRES_PASSWORD=lab --rm postgres:16.1

Read the command carefully. Notice that we are not using docker compose, since we only need a single container for this lab, and the container’s configuration is simple. The container is named concurrency-lab, and it will be deleted when it is stopped.

You can open psql, the PostgreSQL shell, by running:

docker exec -it concurrency-lab psql --username=postgres

Open two psql shells in two separate windows or tabs. If your terminal program supports it, change the background colors of the shells, so you can clearly differentiate them.

Two psql shells, in separate windows.

Next, create the database schema by running the following SQL command in one of the shells:

CREATE TABLE account (
    number INTEGER PRIMARY KEY,
    balance NUMERIC(20, 2)
);

Finally, run the following command in both shells:

\set AUTOCOMMIT off

By default, psql runs with AUTOCOMMIT on, which has the effect of running commit after each command. The experiments in this lab depend on AUTOCOMMIT being turned off.

Step 1: Visibility Experiment

This exercise deals with visibility of the results of one transaction to another transaction.

psql Shell 1: Creating two accounts psql Shell 2: Getting information about the accounts
\set AUTOCOMMIT off \set AUTOCOMMIT off
insert into account (number, balance) values (1, 100.00);  
commit;  
insert into account (number, balance) values (2, 500.00);  
  select * from account where number = 1;
  A) What do you observe? Why do you think you observe this?
  select * from account where number = 2;
  B) What do you observe? Why do you think you observe this?
commit;  
  select * from account where number = 2;
  C) What do you observe? Why do you think you observe this?

Record your observations and explanations.

Step 2: Serializable Schedule Experiment

Initially, the account balances sum to $600. Next, you will attempt a transfer between the accounts.

Shell 1: Transfer $50 from account 2 to 1 Shell 2: Transfer $20 from account 1 to 2
update account set balance = balance - 50 where number = 2;  
  update account set balance = balance + 20 where number = 2;
  A) What do you observe? Why do you think you observe this?
update account set balance = balance + 50 where number = 1;  
commit;  
  B) What do you observe? Why do you think you observe this?
  update account set balance = balance - 20 where number = 1;
  commit;
select * from account; select * from account;

C) Record the account balances reported in each shell. Do the account balances sum to $600?

Step 3: Attempted Non-Serializable Schedule Experiment

Next, you will attempt a transfer with a slightly different sequence of operations.

Shell 1: Transfer $50 from account 2 to 1 Shell 2: Transfer $20 from account 1 to 2
update account set balance = balance - 50 where number = 2;  
  update account set balance = balance - 20 where number = 1;
  A) What do you observe? Why do you think you observe this?
update account set balance = balance + 50 where number = 1;  
B) What do you observe? Why do you think you observe this?  
  update account set balance = balance + 20 where number = 2;
  C) What do you observe? Why do you think you observe this?
commit; commit;
select * from account; select * from account;

D) Record the account balances reported in each shell. Do the account balances sum to $600? Which shell’s transfer affected the database?

E) Explain why the previous experiment’s schedule was serializable, but this experiment’s schedule was non-serializable.

Step 4: Permitted Non-Serializable Schedule Experiment

Next, you will test a sequence of operations that introduces an inconsistency into the database.

Shell 1: Transfer $50 from account 2 to 1 Shell 2: Transfer $20 from account 1 to 2
select * from account;  
A) Record the balances you observe  
  update account set balance = balance + 20 where number = 2;
  update account set balance = balance - 20 where number = 1;
  commit;
update account set balance = (balance from A) - 50 where number = 2;  
update account set balance = balance + 50 where number = 1;  
commit;  
select * from account; select * from account;

B) Record the account balances reported in each shell. Do the account balances sum to $600?

C) Describe how this situation might occur if you wrote Python code to interact with the database.

Hint: The next step will explain why the balances don’t sum to $600.

Step 5: Isolation Levels Experiment

The anomaly you observed in the previous experiment was permitted by PostgreSQL’s default isolation level, “read committed.” Making the isolation level more strict will prevent this kind of anomaly, as you will see in this experiment.

First, type the following in either shell. This will restore the balances so they sum to $600 again.

update account set balance = 100.00 where number = 1;
update account set balance = 500.00 where number = 2;
commit;

Next, run the following commands in each shell. Notice that we start by ending any existing transactions with the ROLLBACK command. Then, we create a new transaction with the REPEATABLE READ isolation level using the BEGIN TRANSACTION command.

Shell 1: Transfer $50 from account 2 to 1 Shell 2: Transfer $20 from account 1 to 2
ROLLBACK; ROLLBACK;
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
select * from account;  
A) Record the balances you observe  
  update account set balance = balance + 20 where number = 2;
  update account set balance = balance - 20 where number = 1;
  commit;
update account set balance = (balance from A) - 50 where number = 2;  
B) What do you observe? Why do you think you observe this?  
update account set balance = balance + 50 where number = 1;  
C) What do you observe? Why do you think you observe this?  
commit;  
D) What do you observe? Why do you think you observe this?  
select * from account; select * from account;

E) Record the account balances reported in each shell. Do the account balances sum to $600? Which shell’s transfer affected the database?

F) Explain why this experiment differed from the previous experiment.

Submit

Submit your observations and explanations on Gradescope.

The assignment will be graded as part of your assignment grade.

Further Reading

Acknowledgements

Thanks to Gordon College’s Databases materials, which served as a basis for this assignment.