diff --git a/database/benchmark/benches/db.rs b/database/benchmark/benches/db.rs
index 38718350..88c28cab 100644
--- a/database/benchmark/benches/db.rs
+++ b/database/benchmark/benches/db.rs
@@ -1,5 +1,7 @@
 //! TODO
 
+use std::time::Instant;
+
 //---------------------------------------------------------------------------------------------------- Import
 use criterion::{black_box, criterion_group, criterion_main, Criterion};
 
@@ -14,7 +16,29 @@ use cuprate_database::{
 use cuprate_database_benchmark::tmp_concrete_env;
 
 //---------------------------------------------------------------------------------------------------- Criterion
-criterion_group!(benches, put, get, get_range, delete);
+criterion_group! {
+    benches,
+    ro_get,
+    ro_len,
+    ro_first,
+    ro_last,
+    ro_is_empty,
+    ro_contains,
+    rw_get,
+    rw_len,
+    rw_first,
+    rw_last,
+    rw_is_empty,
+    rw_contains,
+    put,
+    delete,
+    pop_first,
+    pop_last,
+    get_range,
+    iter,
+    keys,
+    values,
+}
 criterion_main!(benches);
 
 //---------------------------------------------------------------------------------------------------- Constants
@@ -32,7 +56,283 @@ const VALUE: Output = Output {
     tx_idx: 2_353_487,
 };
 
-//---------------------------------------------------------------------------------------------------- Env benchmarks
+//---------------------------------------------------------------------------------------------------- DatabaseRo
+// Read-only table operations.
+// This uses `TxRw + TablesMut` briefly to insert values, then
+// uses `TxRo + Tables` for the actual operation.
+//
+// See further below for using `TxRw + TablesMut` on the same operations.
+
+/// [`DatabaseRo::get`]
+#[named]
+fn ro_get(c: &mut Criterion) {
+    let (env, _tempdir) = tmp_concrete_env();
+    let env_inner = env.env_inner();
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    table.put(&KEY, &VALUE).unwrap();
+    drop(table);
+    tx_rw.commit().unwrap();
+
+    let tx_ro = env_inner.tx_ro().unwrap();
+    let table = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
+
+    c.bench_function(function_name!(), |b| {
+        b.iter(|| {
+            let _value: Output = table.get(black_box(&KEY)).unwrap();
+        });
+    });
+}
+
+/// [`DatabaseRo::len`]
+#[named]
+fn ro_len(c: &mut Criterion) {
+    let (env, _tempdir) = tmp_concrete_env();
+    let env_inner = env.env_inner();
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    table.put(&KEY, &VALUE).unwrap();
+    drop(table);
+    tx_rw.commit().unwrap();
+
+    let tx_ro = env_inner.tx_ro().unwrap();
+    let table = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
+
+    c.bench_function(function_name!(), |b| {
+        b.iter(|| {
+            black_box(table.len()).unwrap();
+        });
+    });
+}
+
+/// [`DatabaseRo::first`]
+#[named]
+fn ro_first(c: &mut Criterion) {
+    let (env, _tempdir) = tmp_concrete_env();
+    let env_inner = env.env_inner();
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    table.put(&KEY, &VALUE).unwrap();
+    drop(table);
+    tx_rw.commit().unwrap();
+
+    let tx_ro = env_inner.tx_ro().unwrap();
+    let table = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
+
+    c.bench_function(function_name!(), |b| {
+        b.iter(|| {
+            let (_, _): (PreRctOutputId, Output) = black_box(table.first()).unwrap();
+        });
+    });
+}
+
+/// [`DatabaseRo::last`]
+#[named]
+fn ro_last(c: &mut Criterion) {
+    let (env, _tempdir) = tmp_concrete_env();
+    let env_inner = env.env_inner();
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    table.put(&KEY, &VALUE).unwrap();
+    drop(table);
+    tx_rw.commit().unwrap();
+
+    let tx_ro = env_inner.tx_ro().unwrap();
+    let table = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
+
+    c.bench_function(function_name!(), |b| {
+        b.iter(|| {
+            let (_, _): (PreRctOutputId, Output) = black_box(table.last()).unwrap();
+        });
+    });
+}
+
+/// [`DatabaseRo::is_empty`]
+#[named]
+fn ro_is_empty(c: &mut Criterion) {
+    let (env, _tempdir) = tmp_concrete_env();
+    let env_inner = env.env_inner();
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    table.put(&KEY, &VALUE).unwrap();
+    drop(table);
+    tx_rw.commit().unwrap();
+
+    let tx_ro = env_inner.tx_ro().unwrap();
+    let table = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
+
+    c.bench_function(function_name!(), |b| {
+        b.iter(|| {
+            black_box(table.is_empty()).unwrap();
+        });
+    });
+}
+
+/// [`DatabaseRo::contains`]
+#[named]
+fn ro_contains(c: &mut Criterion) {
+    let (env, _tempdir) = tmp_concrete_env();
+    let env_inner = env.env_inner();
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    table.put(&KEY, &VALUE).unwrap();
+    drop(table);
+    tx_rw.commit().unwrap();
+
+    let tx_ro = env_inner.tx_ro().unwrap();
+    let table = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
+
+    c.bench_function(function_name!(), |b| {
+        b.iter(|| {
+            table.contains(black_box(&KEY)).unwrap();
+        });
+    });
+}
+
+//---------------------------------------------------------------------------------------------------- DatabaseRo (using TxRw)
+// These are the same benchmarks as above, but it uses a
+// `TxRw` and a `TablesMut` instead to ensure our read/write tables
+// using read operations perform the same as normal read-only tables.
+
+/// [`DatabaseRo::get`]
+#[named]
+fn rw_get(c: &mut Criterion) {
+    let (env, _tempdir) = tmp_concrete_env();
+    let env_inner = env.env_inner();
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    table.put(&KEY, &VALUE).unwrap();
+    drop(table);
+    tx_rw.commit().unwrap();
+
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    c.bench_function(function_name!(), |b| {
+        b.iter(|| {
+            let _value: Output = table.get(black_box(&KEY)).unwrap();
+        });
+    });
+}
+
+/// [`DatabaseRo::len`]
+#[named]
+fn rw_len(c: &mut Criterion) {
+    let (env, _tempdir) = tmp_concrete_env();
+    let env_inner = env.env_inner();
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    table.put(&KEY, &VALUE).unwrap();
+    drop(table);
+    tx_rw.commit().unwrap();
+
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    c.bench_function(function_name!(), |b| {
+        b.iter(|| {
+            black_box(table.len()).unwrap();
+        });
+    });
+}
+
+/// [`DatabaseRo::first`]
+#[named]
+fn rw_first(c: &mut Criterion) {
+    let (env, _tempdir) = tmp_concrete_env();
+    let env_inner = env.env_inner();
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    table.put(&KEY, &VALUE).unwrap();
+    drop(table);
+    tx_rw.commit().unwrap();
+
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    c.bench_function(function_name!(), |b| {
+        b.iter(|| {
+            let (_, _): (PreRctOutputId, Output) = black_box(table.first()).unwrap();
+        });
+    });
+}
+
+/// [`DatabaseRo::last`]
+#[named]
+fn rw_last(c: &mut Criterion) {
+    let (env, _tempdir) = tmp_concrete_env();
+    let env_inner = env.env_inner();
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    table.put(&KEY, &VALUE).unwrap();
+    drop(table);
+    tx_rw.commit().unwrap();
+
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    c.bench_function(function_name!(), |b| {
+        b.iter(|| {
+            let (_, _): (PreRctOutputId, Output) = black_box(table.last()).unwrap();
+        });
+    });
+}
+
+/// [`DatabaseRo::is_empty`]
+#[named]
+fn rw_is_empty(c: &mut Criterion) {
+    let (env, _tempdir) = tmp_concrete_env();
+    let env_inner = env.env_inner();
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    table.put(&KEY, &VALUE).unwrap();
+    drop(table);
+    tx_rw.commit().unwrap();
+
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    c.bench_function(function_name!(), |b| {
+        b.iter(|| {
+            black_box(table.is_empty()).unwrap();
+        });
+    });
+}
+
+/// [`DatabaseRo::contains`]
+#[named]
+fn rw_contains(c: &mut Criterion) {
+    let (env, _tempdir) = tmp_concrete_env();
+    let env_inner = env.env_inner();
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    table.put(&KEY, &VALUE).unwrap();
+    drop(table);
+    tx_rw.commit().unwrap();
+
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    c.bench_function(function_name!(), |b| {
+        b.iter(|| {
+            table.contains(black_box(&KEY)).unwrap();
+        });
+    });
+}
+
+//---------------------------------------------------------------------------------------------------- DatabaseRw
 /// [`DatabaseRw::put`]
 #[named]
 fn put(c: &mut Criterion) {
@@ -51,23 +351,117 @@ fn put(c: &mut Criterion) {
     });
 }
 
-/// [`DatabaseRo::get`]
+/// [`DatabaseRw::delete`]
 #[named]
-fn get(c: &mut Criterion) {
+fn delete(c: &mut Criterion) {
     let (env, _tempdir) = tmp_concrete_env();
     let env_inner = env.env_inner();
     let tx_rw = env_inner.tx_rw().unwrap();
     let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
 
-    table.put(&KEY, &VALUE).unwrap();
+    let mut key = KEY;
 
     c.bench_function(function_name!(), |b| {
-        b.iter(|| {
-            let _value: Output = table.get(black_box(&KEY)).unwrap();
+        b.iter_custom(|iters| {
+            for _ in 0..iters {
+                table.put(&key, &VALUE).unwrap();
+                key.amount += 1;
+            }
+
+            key = KEY;
+
+            let start = Instant::now();
+            for _ in 0..iters {
+                table.delete(&key).unwrap();
+                key.amount += 1;
+            }
+            start.elapsed()
         });
     });
 }
 
+/// [`DatabaseRw::pop_first`]
+#[named]
+fn pop_first(c: &mut Criterion) {
+    let (env, _tempdir) = tmp_concrete_env();
+    let env_inner = env.env_inner();
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    let mut key = KEY;
+
+    c.bench_function(function_name!(), |b| {
+        b.iter_custom(|iters| {
+            for _ in 0..iters {
+                table.put(&key, &VALUE).unwrap();
+                key.amount += 1;
+            }
+
+            key = KEY;
+
+            let start = Instant::now();
+            for _ in 0..iters {
+                table.pop_first().unwrap();
+                key.amount += 1;
+            }
+            start.elapsed()
+        });
+    });
+}
+
+/// [`DatabaseRw::pop_last`]
+#[named]
+fn pop_last(c: &mut Criterion) {
+    let (env, _tempdir) = tmp_concrete_env();
+    let env_inner = env.env_inner();
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    let mut key = KEY;
+
+    c.bench_function(function_name!(), |b| {
+        b.iter_custom(|iters| {
+            for _ in 0..iters {
+                table.put(&key, &VALUE).unwrap();
+                key.amount += 1;
+            }
+
+            key = KEY;
+
+            let start = Instant::now();
+            for _ in 0..iters {
+                table.pop_last().unwrap();
+                key.amount += 1;
+            }
+            start.elapsed()
+        });
+    });
+}
+
+// TODO: waiting on PR 102
+// /// [`DatabaseRw::take`]
+// #[named]
+// fn take(c: &mut Criterion) {
+//     let (env, _tempdir) = tmp_concrete_env();
+//     let env_inner = env.env_inner();
+//     let tx_rw = env_inner.tx_rw().unwrap();
+//     let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+//     let mut key = KEY;
+//     for _ in 0..100 {
+//         table.put(&key, &VALUE).unwrap();
+//         key.amount += 1;
+//     }
+
+//     c.bench_function(function_name!(), |b| {
+//         b.iter(|| {
+//             table.put(&KEY, &VALUE).unwrap();
+//             let value: Output = black_box(table.take(&black_box(KEY)).unwrap());
+//         });
+//     });
+// }
+
+//---------------------------------------------------------------------------------------------------- DatabaseIter
 /// [`DatabaseRo::get_range`]
 #[named]
 fn get_range(c: &mut Criterion) {
@@ -98,9 +492,9 @@ fn get_range(c: &mut Criterion) {
     });
 }
 
-/// [`DatabaseRw::delete`]
+/// [`DatabaseRo::iter`]
 #[named]
-fn delete(c: &mut Criterion) {
+fn iter(c: &mut Criterion) {
     let (env, _tempdir) = tmp_concrete_env();
     let env_inner = env.env_inner();
     let tx_rw = env_inner.tx_rw().unwrap();
@@ -112,33 +506,78 @@ fn delete(c: &mut Criterion) {
         key.amount += 1;
     }
 
+    drop(table);
+    TxRw::commit(tx_rw).unwrap();
+
+    let tx_ro = env_inner.tx_ro().unwrap();
+    let table = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
+
     c.bench_function(function_name!(), |b| {
         b.iter(|| {
-            table.put(&KEY, &VALUE).unwrap();
-            table.delete(&black_box(KEY)).unwrap();
+            let iter = black_box(table.iter()).unwrap();
+            for result in iter {
+                let _: (PreRctOutputId, Output) = black_box(result.unwrap());
+            }
         });
     });
 }
 
-// TODO: waiting on PR 102
-// /// [`DatabaseRw::take`]
-// #[named]
-// fn take(c: &mut Criterion) {
-//     let (env, _tempdir) = tmp_concrete_env();
-//     let env_inner = env.env_inner();
-//     let tx_rw = env_inner.tx_rw().unwrap();
-//     let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+/// [`DatabaseRo::keys`]
+#[named]
+fn keys(c: &mut Criterion) {
+    let (env, _tempdir) = tmp_concrete_env();
+    let env_inner = env.env_inner();
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
 
-//     let mut key = KEY;
-//     for _ in 0..100 {
-//         table.put(&key, &VALUE).unwrap();
-//         key.amount += 1;
-//     }
+    let mut key = KEY;
+    for _ in 0..100 {
+        table.put(&key, &VALUE).unwrap();
+        key.amount += 1;
+    }
 
-//     c.bench_function(function_name!(), |b| {
-//         b.iter(|| {
-//             table.put(&KEY, &VALUE).unwrap();
-//             let value: Output = black_box(table.take(&black_box(KEY)).unwrap());
-//         });
-//     });
-// }
+    drop(table);
+    TxRw::commit(tx_rw).unwrap();
+
+    let tx_ro = env_inner.tx_ro().unwrap();
+    let table = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
+
+    c.bench_function(function_name!(), |b| {
+        b.iter(|| {
+            let keys = black_box(table.keys()).unwrap();
+            for result in keys {
+                let _: PreRctOutputId = black_box(result.unwrap());
+            }
+        });
+    });
+}
+
+/// [`DatabaseRo::values`]
+#[named]
+fn values(c: &mut Criterion) {
+    let (env, _tempdir) = tmp_concrete_env();
+    let env_inner = env.env_inner();
+    let tx_rw = env_inner.tx_rw().unwrap();
+    let mut table = env_inner.open_db_rw::<Outputs>(&tx_rw).unwrap();
+
+    let mut key = KEY;
+    for _ in 0..100 {
+        table.put(&key, &VALUE).unwrap();
+        key.amount += 1;
+    }
+
+    drop(table);
+    TxRw::commit(tx_rw).unwrap();
+
+    let tx_ro = env_inner.tx_ro().unwrap();
+    let table = env_inner.open_db_ro::<Outputs>(&tx_ro).unwrap();
+
+    c.bench_function(function_name!(), |b| {
+        b.iter(|| {
+            let values = black_box(table.values()).unwrap();
+            for result in values {
+                let _: Output = black_box(result.unwrap());
+            }
+        });
+    });
+}
diff --git a/database/benchmark/benches/env.rs b/database/benchmark/benches/env.rs
index e0420f03..3a0c7880 100644
--- a/database/benchmark/benches/env.rs
+++ b/database/benchmark/benches/env.rs
@@ -15,7 +15,7 @@ use cuprate_database::{
 use cuprate_database_benchmark::tmp_concrete_env;
 
 //---------------------------------------------------------------------------------------------------- Criterion
-criterion_group!(
+criterion_group! {
     benches,
     open,
     env_inner,
@@ -26,7 +26,7 @@ criterion_group!(
     resize,
     current_map_size,
     disk_size_bytes,
-);
+}
 criterion_main!(benches);
 
 //---------------------------------------------------------------------------------------------------- Env benchmarks
diff --git a/database/benchmark/benches/storable.rs b/database/benchmark/benches/storable.rs
index d1b340d8..baa01b9f 100644
--- a/database/benchmark/benches/storable.rs
+++ b/database/benchmark/benches/storable.rs
@@ -11,13 +11,13 @@ use cuprate_database::{
 };
 
 //---------------------------------------------------------------------------------------------------- Criterion
-criterion_group!(
+criterion_group! {
     benches,
     pre_rct_output_id_as_bytes,
     pre_rct_output_id_from_bytes,
     output_as_bytes,
     output_from_bytes
-);
+}
 criterion_main!(benches);
 
 //---------------------------------------------------------------------------------------------------- Constants