diff --git a/conn.go b/conn.go
index a1542b1c..bcbf88ad 100644
--- a/conn.go
+++ b/conn.go
@@ -75,6 +75,17 @@ type ConnConfig struct {
 	RuntimeParams     map[string]string                     // Run-time parameters to set on connection as session default values (e.g. search_path or application_name)
 	OnNotice          NoticeHandler                         // Callback function called when a notice response is received.
 	CustomConnInfo    func(*Conn) (*pgtype.ConnInfo, error) // Callback function to implement connection strategies for different backends. crate, pgbouncer, pgpool, etc.
+
+	// PreferSimpleProtocol disables implicit prepared statement usage. By default
+	// pgx automatically uses the unnamed prepared statement for Query and
+	// QueryRow. It also uses a prepared statement when Exec has arguments. This
+	// can improve performance due to being able to use the binary format. It also
+	// does not rely on client side parameter sanitization. However, it does incur
+	// two round-trips per query and may be incompatible proxies such as
+	// PGBouncer. Setting PreferSimpleProtocol causes the simple protocol to be
+	// used by default. The same functionality can be controlled on a per query
+	// basis by setting QueryExOptions.SimpleProtocol.
+	PreferSimpleProtocol bool
 }
 
 func (cc *ConnConfig) networkAddress() (network, address string) {
@@ -1586,7 +1597,7 @@ func (c *Conn) execEx(ctx context.Context, sql string, options *QueryExOptions,
 		err = c.termContext(err)
 	}()
 
-	if options != nil && options.SimpleProtocol {
+	if (options == nil && c.config.PreferSimpleProtocol) || (options != nil && options.SimpleProtocol) {
 		err = c.sanitizeAndSendSimpleQuery(sql, arguments...)
 		if err != nil {
 			return "", err
diff --git a/conn_test.go b/conn_test.go
index d8781158..c1cb4ebe 100644
--- a/conn_test.go
+++ b/conn_test.go
@@ -255,6 +255,31 @@ func TestConnectWithConnectionRefused(t *testing.T) {
 	}
 }
 
+func TestConnectWithPreferSimpleProtocol(t *testing.T) {
+	t.Parallel()
+
+	connConfig := *defaultConnConfig
+	connConfig.PreferSimpleProtocol = true
+
+	conn := mustConnect(t, connConfig)
+	defer closeConn(t, conn)
+
+	// If simple protocol is used we should be able to correctly scan the result
+	// into a pgtype.Text as the integer will have been encoded in text.
+
+	var s pgtype.Text
+	err := conn.QueryRow("select $1::int4", 42).Scan(&s)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if s.Get() != "42" {
+		t.Fatalf(`expected "42", got %v`, s)
+	}
+
+	ensureConnValid(t, conn)
+}
+
 func TestConnectCustomDialer(t *testing.T) {
 	t.Parallel()
 
diff --git a/query.go b/query.go
index b90d43b2..3576091f 100644
--- a/query.go
+++ b/query.go
@@ -393,7 +393,7 @@ func (c *Conn) QueryEx(ctx context.Context, sql string, options *QueryExOptions,
 		return rows, rows.err
 	}
 
-	if options != nil && options.SimpleProtocol {
+	if (options == nil && c.config.PreferSimpleProtocol) || (options != nil && options.SimpleProtocol) {
 		err = c.sanitizeAndSendSimpleQuery(sql, args...)
 		if err != nil {
 			rows.fatal(err)