diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+# v1.2.3.0
+
+## Non-breaking
+
+- Add `Semigroup`/`Monoid` instances for `Mode` and `IsolationLevel`, combining via `max` with `mempty` as the weakest value, so composed transaction requirements escalate to the strictest one explicitly requested.
+
 # v1.2.2.1
 
 - Conform to the new `hasql` API (v2.0)
diff --git a/hasql-transaction.cabal b/hasql-transaction.cabal
--- a/hasql-transaction.cabal
+++ b/hasql-transaction.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.0
 name: hasql-transaction
-version: 1.2.2.1
+version: 1.2.3.0
 category: Hasql, Database, PostgreSQL
 synopsis:
   Composable abstraction over retryable transactions for Hasql
diff --git a/src/library/Hasql/Transaction/Config.hs b/src/library/Hasql/Transaction/Config.hs
--- a/src/library/Hasql/Transaction/Config.hs
+++ b/src/library/Hasql/Transaction/Config.hs
@@ -12,6 +12,18 @@
   deriving (Show, Eq, Ord, Enum, Bounded)
 
 -- |
+-- Combines two modes by picking the one that grants more capability.
+--
+-- 'mempty' is 'Read', the identity of 'max': it never overrides an
+-- explicit 'Write' requirement, so a piece of a composed transaction
+-- that needs to write always wins over pieces that don't care.
+instance Semigroup Mode where
+  (<>) = max
+
+instance Monoid Mode where
+  mempty = minBound
+
+-- |
 -- For reference see
 -- <http://www.postgresql.org/docs/current/static/transaction-iso.html the Postgres' documentation>.
 data IsolationLevel
@@ -19,3 +31,15 @@
   | RepeatableRead
   | Serializable
   deriving (Show, Eq, Ord, Enum, Bounded)
+
+-- |
+-- Combines two isolation levels by picking the stricter one.
+--
+-- 'mempty' is 'ReadCommitted', the identity of 'max': it never overrides
+-- an explicit stricter requirement, so a piece of a composed transaction
+-- that needs e.g. 'Serializable' always wins over pieces that don't care.
+instance Semigroup IsolationLevel where
+  (<>) = max
+
+instance Monoid IsolationLevel where
+  mempty = minBound
