Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 18936] ファイルの概要

コミット

このコミットは、Go言語の標準ライブラリであるdatabase/sqlパッケージの例とドキュメントに、rows.Close()defer呼び出しを追加するものです。これは、リソースリークを防ぎ、コードの堅牢性を高めるためのベストプラクティスを強調することを目的としています。特に、Rows.Next()falseを返した場合にRowsが自動的に閉じられるという挙動があるものの、ループが途中で中断されたり、早期にリターンされたりするケースにおいて、明示的なrows.Close()の呼び出しが重要であることを示しています。

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/50ca1a52ca39bdd76bb5f999a67450f5984ebba2

元コミット内容

database/sql: add "defer rows.Close()" to the example code.

Strictly speaking, it's not necessary in example_test.go, as the
Rows.Close docs say that "If Next returns false, the Rows are closed
automatically". However, if the for loop breaks or returns early, it's
not obvious that you'll leak unless you explicitly call Rows.Close.

LGTM=bradfitz
R=bradfitz
CC=golang-codereviews, rsc
https://golang.org/cl/79330043

変更の背景

Goのdatabase/sqlパッケージを使用する際、データベースクエリの結果を表すRowsオブジェクトは、使用後に適切に閉じる必要があります。Rows.Close()メソッドは、関連するデータベース接続リソースを解放するために呼び出されます。

コミットメッセージが示唆するように、Rows.Next()falseを返した場合(つまり、すべての行が処理された場合)、Rowsは自動的に閉じられます。しかし、これは開発者にとって直感的ではない場合があります。特に、以下のようなシナリオではリソースリークが発生する可能性があります。

  1. ループの早期終了: for rows.Next()ループ内でエラーが発生したり、特定の条件に基づいてループがbreakまたはreturnで中断されたりした場合、rows.Close()が自動的に呼び出されないため、データベース接続が解放されずに残ってしまう可能性があります。
  2. エラーハンドリング: クエリの実行後、rows.Next()を呼び出す前にエラーが発生した場合、rows.Close()が呼び出されない可能性があります。

このコミットは、これらの潜在的なリソースリークを防ぐためのベストプラクティスとして、defer rows.Close()を例とドキュメントに追加することで、開発者がより堅牢なコードを書くことを奨励しています。deferを使用することで、関数の終了時に必ずrows.Close()が呼び出されることが保証され、リソース管理が簡素化されます。

前提知識の解説

Go言語のdatabase/sqlパッケージ

database/sqlパッケージは、Go言語でSQLデータベースを操作するための汎用的なインターフェースを提供します。このパッケージは、特定のデータベースドライバに依存しない抽象化レイヤーを提供し、開発者は統一されたAPIで様々なデータベース(PostgreSQL, MySQL, SQLiteなど)とやり取りできます。

主要なコンポーネントは以下の通りです。

  • DB: データベースへの接続プールを表します。
  • Stmt: プリペアドステートメントを表します。
  • Tx: トランザクションを表します。
  • Rows: Queryメソッドによって返される結果セットの行を表します。

Rowsオブジェクトとリソース管理

db.Query()stmt.Query()などのメソッドを呼び出すと、*sql.Rows型のオブジェクトが返されます。このRowsオブジェクトは、データベースから取得した結果セットをイテレートするために使用されます。Rowsオブジェクトは、データベース接続に関連するリソース(例えば、カーソルやネットワークソケット)を保持しています。これらのリソースは、Rowsオブジェクトが不要になったときに解放される必要があります。

Rows.Close()メソッド

Rows.Close()メソッドは、Rowsオブジェクトに関連付けられたリソースを明示的に解放するために呼び出されます。このメソッドは、結果セットの処理が完了した後、またはエラーが発生して処理を中断する必要がある場合に非常に重要です。

deferキーワード

Go言語のdeferキーワードは、関数がリターンする直前に実行される関数呼び出しをスケジュールするために使用されます。deferステートメントは、リソースのクリーンアップ(ファイルのクローズ、ロックの解放、データベース接続のクローズなど)を確実に行うためのイディオムとして広く利用されています。

deferの主な特徴は以下の通りです。

  • deferされた関数は、それを囲む関数が実行を終了する直前(returnステートメントの直後、またはパニックが発生した場合)に実行されます。
  • 複数のdeferステートメントがある場合、それらはLIFO(Last-In, First-Out)の順序で実行されます。つまり、最後にdeferされたものが最初に実行されます。
  • deferされた関数の引数は、deferステートメントが評価された時点で評価されます。

このコミットでは、defer rows.Close()を使用することで、Queryを呼び出した関数がどのような経路で終了しても(正常終了、エラーによる早期リターンなど)、必ずrows.Close()が呼び出され、リソースリークが防止されることを保証しています。

技術的詳細

このコミットの技術的な核心は、database/sqlパッケージにおけるRowsオブジェクトのライフサイクル管理と、Goのdeferキーワードの適切な使用法にあります。

Rowsオブジェクトは、データベースから取得した結果セットを保持し、その背後にはデータベース接続に関連するリソースが存在します。これらのリソースは有限であり、適切に解放されないと、以下のような問題を引き起こす可能性があります。

  • データベース接続の枯渇: 接続プール内の利用可能な接続が減少し、新しいデータベース操作がブロックされる可能性があります。
  • メモリリーク: Rowsオブジェクトが保持するデータや関連リソースが解放されず、アプリケーションのメモリ使用量が増加します。
  • パフォーマンスの低下: 未解放のリソースがデータベースサーバー側にも負荷をかけ、全体的なパフォーマンスに影響を与える可能性があります。

Rows.Close()のドキュメントには、「Nextfalseを返した場合、Rowsは自動的に閉じられる」と記載されています。これは、結果セットのすべての行を正常にイテレートし終えた場合には、明示的にClose()を呼び出す必要がないことを意味します。しかし、これは特定の条件下でのみ保証される挙動です。

コミットメッセージが指摘するように、以下のようなケースでは自動クローズが期待できません。

  1. ループの途中でbreakまたはreturn:

    for rows.Next() {
        // ... 処理 ...
        if someCondition {
            break // または return
        }
    }
    // この場合、rows.Close() は自動的に呼び出されない
    

    このシナリオでは、rows.Next()falseを返す前にループが終了するため、Rowsオブジェクトは開いたままになり、関連するリソースがリークします。

  2. rows.Next()を呼び出す前のエラー:

    rows, err := db.Query("SELECT name FROM users")
    if err != nil {
        log.Fatal(err) // rows.Close() は呼び出されない
    }
    // ... rows.Next() を呼び出す前にエラーが発生した場合
    

    db.Query()がエラーを返した場合、rowsnilになるか、有効なRowsオブジェクトではない可能性があります。この場合、rows.Close()を呼び出すことはできません。しかし、rowsが有効なオブジェクトとして返されたものの、その後の処理でrows.Next()が一度も呼び出されずにエラーが発生した場合、リソースリークが発生します。

defer rows.Close()を使用することで、これらのエッジケースを網羅的にカバーできます。deferは、それを囲む関数が正常に終了するか、パニックによって終了するかにかかわらず、必ず実行されるため、リソースの解放を保証します。これにより、開発者はリソース管理の複雑さから解放され、よりビジネスロジックに集中できるようになります。

この変更は、単に例コードを修正するだけでなく、database/sqlパッケージのユーザーに対する重要なガイダンスを提供し、Goアプリケーションの堅牢性と信頼性を向上させるためのベストプラクティスを確立するものです。

コアとなるコードの変更箇所

このコミットでは、以下の2つのファイルが変更されています。

  1. src/pkg/database/sql/example_test.go
  2. src/pkg/database/sql/sql.go

src/pkg/database/sql/example_test.go の変更

--- a/src/pkg/database/sql/example_test.go
+++ b/src/pkg/database/sql/example_test.go
@@ -18,6 +18,7 @@ func ExampleDB_Query() {
 	if err != nil {
 		log.Fatal(err)
 	}
+	defer rows.Close()
 	for rows.Next() {
 		var name string
 		if err := rows.Scan(&name); err != nil {

src/pkg/database/sql/sql.go の変更

--- a/src/pkg/database/sql/sql.go
+++ b/src/pkg/database/sql/sql.go
@@ -1494,6 +1494,7 @@ func (s *Stmt) finalClose() error {
 //
 //     rows, err := db.Query("SELECT ...")
 //     ...
+//     defer rows.Close()
 //     for rows.Next() {
 //         var id int
 //         var name string

コアとなるコードの解説

src/pkg/database/sql/example_test.go の変更点

ExampleDB_Query()関数は、database/sqlパッケージのQueryメソッドの使用例を示すテストコードです。 変更前は、rows, err := db.Query(...)の後に直接for rows.Next()ループが始まっていました。 変更後、rows, err := db.Query(...)の直後にdefer rows.Close()が追加されました。

この変更により、ExampleDB_Query()関数がどのような経路で終了しても(例えば、log.Fatal(err)でプログラムが終了する場合や、将来的にループ内でbreakreturnが追加された場合など)、rows.Close()が確実に呼び出されるようになります。これは、データベース接続に関連するリソースのリークを防ぐためのGoにおけるイディオムであり、ベストプラクティスです。

src/pkg/database/sql/sql.go の変更点

このファイルはdatabase/sqlパッケージの主要な実装ファイルであり、変更箇所はコメントブロック内にあります。 具体的には、Rowsオブジェクトの利用例を示すコメントに、defer rows.Close()の行が追加されました。

これはコードの動作に直接影響を与える変更ではありませんが、database/sqlパッケージのドキュメントと例を改善し、ユーザーがRowsオブジェクトを扱う際の正しいパターンを学ぶのに役立ちます。ドキュメント内の例は、開発者がコードを書く際の重要な参考資料となるため、この追加は非常に価値があります。

両方の変更は、database/sqlパッケージのユーザーがリソースを適切に管理し、堅牢なアプリケーションを構築するためのガイダンスを強化することを目的としています。

関連リンク

参考にした情報源リンク

[インデックス 18936] ファイルの概要

コミット

このコミットは、Go言語の標準ライブラリであるdatabase/sqlパッケージの例とドキュメントに、rows.Close()defer呼び出しを追加するものです。これは、リソースリークを防ぎ、コードの堅牢性を高めるためのベストプラクティスを強調することを目的としています。特に、Rows.Next()falseを返した場合にRowsが自動的に閉じられるという挙動があるものの、ループが途中で中断されたり、早期にリターンされたりするケースにおいて、明示的なrows.Close()の呼び出しが重要であることを示しています。

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/50ca1a52ca39bdd76bb5f999a67450f5984ebba2

元コミット内容

database/sql: add "defer rows.Close()" to the example code.

Strictly speaking, it's not necessary in example_test.go, as the
Rows.Close docs say that "If Next returns false, the Rows are closed
automatically". However, if the for loop breaks or returns early, it's
not obvious that you'll leak unless you explicitly call Rows.Close.

LGTM=bradfitz
R=bradfitz
CC=golang-codereviews, rsc
https://golang.org/cl/79330043

変更の背景

Goのdatabase/sqlパッケージを使用する際、データベースクエリの結果を表すRowsオブジェクトは、使用後に適切に閉じる必要があります。Rows.Close()メソッドは、関連するデータベース接続リソースを解放するために呼び出されます。

コミットメッセージが示唆するように、Rows.Next()falseを返した場合(つまり、すべての行が処理された場合)、Rowsは自動的に閉じられます。しかし、これは開発者にとって直感的ではない場合があります。特に、以下のようなシナリオではリソースリークが発生する可能性があります。

  1. ループの早期終了: for rows.Next()ループ内でエラーが発生したり、特定の条件に基づいてループがbreakまたはreturnで中断されたりした場合、rows.Close()が自動的に呼び出されないため、データベース接続が解放されずに残ってしまう可能性があります。
  2. エラーハンドリング: クエリの実行後、rows.Next()を呼び出す前にエラーが発生した場合、rows.Close()が呼び出されない可能性があります。

このコミットは、これらの潜在的なリソースリークを防ぐためのベストプラクティスとして、defer rows.Close()を例とドキュメントに追加することで、開発者がより堅牢なコードを書くことを奨励しています。deferを使用することで、関数の終了時に必ずrows.Close()が呼び出されることが保証され、リソース管理が簡素化されます。

前提知識の解説

Go言語のdatabase/sqlパッケージ

database/sqlパッケージは、Go言語でSQLデータベースを操作するための汎用的なインターフェースを提供します。このパッケージは、特定のデータベースドライバに依存しない抽象化レイヤーを提供し、開発者は統一されたAPIで様々なデータベース(PostgreSQL, MySQL, SQLiteなど)とやり取りできます。

主要なコンポーネントは以下の通りです。

  • DB: データベースへの接続プールを表します。
  • Stmt: プリペアドステートメントを表します。
  • Tx: トランザクションを表します。
  • Rows: Queryメソッドによって返される結果セットの行を表します。

Rowsオブジェクトとリソース管理

db.Query()stmt.Query()などのメソッドを呼び出すと、*sql.Rows型のオブジェクトが返されます。このRowsオブジェクトは、データベースから取得した結果セットをイテレートするために使用されます。Rowsオブジェクトは、データベース接続に関連するリソース(例えば、カーソルやネットワークソケット)を保持しています。これらのリソースは、Rowsオブジェクトが不要になったときに解放される必要があります。

Rows.Close()メソッド

Rows.Close()メソッドは、Rowsオブジェクトに関連付けられたリソースを明示的に解放するために呼び出されます。このメソッドは、結果セットの処理が完了した後、またはエラーが発生して処理を中断する必要がある場合に非常に重要です。

deferキーワード

Go言語のdeferキーワードは、関数がリターンする直前に実行される関数呼び出しをスケジュールするために使用されます。deferステートメントは、リソースのクリーンアップ(ファイルのクローズ、ロックの解放、データベース接続のクローズなど)を確実に行うためのイディオムとして広く利用されています。

deferの主な特徴は以下の通りです。

  • deferされた関数は、それを囲む関数が実行を終了する直前(returnステートメントの直後、またはパニックが発生した場合)に実行されます。
  • 複数のdeferステートメントがある場合、それらはLIFO(Last-In, First-Out)の順序で実行されます。つまり、最後にdeferされたものが最初に実行されます。
  • deferされた関数の引数は、deferステートメントが評価された時点で評価されます。

このコミットでは、defer rows.Close()を使用することで、Queryを呼び出した関数がどのような経路で終了しても(正常終了、エラーによる早期リターンなど)、必ずrows.Close()が呼び出され、リソースリークが防止されることを保証しています。

技術的詳細

このコミットの技術的な核心は、database/sqlパッケージにおけるRowsオブジェクトのライフサイクル管理と、Goのdeferキーワードの適切な使用法にあります。

Rowsオブジェクトは、データベースから取得した結果セットを保持し、その背後にはデータベース接続に関連するリソースが存在します。これらのリソースは有限であり、適切に解放されないと、以下のような問題を引き起こす可能性があります。

  • データベース接続の枯渇: 接続プール内の利用可能な接続が減少し、新しいデータベース操作がブロックされる可能性があります。
  • メモリリーク: Rowsオブジェクトが保持するデータや関連リソースが解放されず、アプリケーションのメモリ使用量が増加します。
  • パフォーマンスの低下: 未解放のリソースがデータベースサーバー側にも負荷をかけ、全体的なパフォーマンスに影響を与える可能性があります。

Rows.Close()のドキュメントには、「Nextfalseを返した場合、Rowsは自動的に閉じられる」と記載されています。これは、結果セットのすべての行を正常にイテレートし終えた場合には、明示的にClose()を呼び出す必要がないことを意味します。しかし、これは特定の条件下でのみ保証される挙動です。

コミットメッセージが指摘するように、以下のようなケースでは自動クローズが期待できません。

  1. ループの途中でbreakまたはreturn:

    for rows.Next() {
        // ... 処理 ...
        if someCondition {
            break // または return
        }
    }
    // この場合、rows.Close() は自動的に呼び出されない
    

    このシナリオでは、rows.Next()falseを返す前にループが終了するため、Rowsオブジェクトは開いたままになり、関連するリソースがリークします。

  2. rows.Next()を呼び出す前のエラー:

    rows, err := db.Query("SELECT name FROM users")
    if err != nil {
        log.Fatal(err) // rows.Close() は呼び出されない
    }
    // ... rows.Next() を呼び出す前にエラーが発生した場合
    

    db.Query()がエラーを返した場合、rowsnilになるか、有効なRowsオブジェクトではない可能性があります。この場合、rows.Close()を呼び出すことはできません。しかし、rowsが有効なオブジェクトとして返されたものの、その後の処理でrows.Next()が一度も呼び出されずにエラーが発生した場合、リソースリークが発生します。

defer rows.Close()を使用することで、これらのエッジケースを網羅的にカバーできます。deferは、それを囲む関数が正常に終了するか、パニックによって終了するかにかかわらず、必ず実行されるため、リソースの解放を保証します。これにより、開発者はリソース管理の複雑さから解放され、よりビジネスロジックに集中できるようになります。

この変更は、単に例コードを修正するだけでなく、database/sqlパッケージのユーザーに対する重要なガイダンスを提供し、Goアプリケーションの堅牢性と信頼性を向上させるためのベストプラクティスを確立するものです。

コアとなるコードの変更箇所

このコミットでは、以下の2つのファイルが変更されています。

  1. src/pkg/database/sql/example_test.go
  2. src/pkg/database/sql/sql.go

src/pkg/database/sql/example_test.go の変更

--- a/src/pkg/database/sql/example_test.go
+++ b/src/pkg/database/sql/example_test.go
@@ -18,6 +18,7 @@ func ExampleDB_Query() {
 	if err != nil {
 		log.Fatal(err)
 	}
+	defer rows.Close()
 	for rows.Next() {
 		var name string
 		if err := rows.Scan(&name); err != nil {

src/pkg/database/sql/sql.go の変更

--- a/src/pkg/database/sql/sql.go
+++ b/src/pkg/database/sql/sql.go
@@ -1494,6 +1494,7 @@ func (s *Stmt) finalClose() error {
 //
 //     rows, err := db.Query("SELECT ...")
 //     ...
+//     defer rows.Close()
 //     for rows.Next() {
 //         var id int
 //         var name string

コアとなるコードの解説

src/pkg/database/sql/example_test.go の変更点

ExampleDB_Query()関数は、database/sqlパッケージのQueryメソッドの使用例を示すテストコードです。 変更前は、rows, err := db.Query(...)の後に直接for rows.Next()ループが始まっていました。 変更後、rows, err := db.Query(...)の直後にdefer rows.Close()が追加されました。

この変更により、ExampleDB_Query()関数がどのような経路で終了しても(例えば、log.Fatal(err)でプログラムが終了する場合や、将来的にループ内でbreakreturnが追加された場合など)、rows.Close()が確実に呼び出されるようになります。これは、データベース接続に関連するリソースのリークを防ぐためのGoにおけるイディオムであり、ベストプラクティスです。

src/pkg/database/sql/sql.go の変更点

このファイルはdatabase/sqlパッケージの主要な実装ファイルであり、変更箇所はコメントブロック内にあります。 具体的には、Rowsオブジェクトの利用例を示すコメントに、defer rows.Close()の行が追加されました。

これはコードの動作に直接影響を与える変更ではありませんが、database/sqlパッケージのドキュメントと例を改善し、ユーザーがRowsオブジェクトを扱う際の正しいパターンを学ぶのに役立ちます。ドキュメント内の例は、開発者がコードを書く際の重要な参考資料となるため、この追加は非常に価値があります。

両方の変更は、database/sqlパッケージのユーザーがリソースを適切に管理し、堅牢なアプリケーションを構築するためのガイダンスを強化することを目的としています。

関連リンク

参考にした情報源リンク