web-dev-qa-db-ja.com

「テーブルから選択*」が悪い習慣と見なされるのはなぜですか

昨日、「趣味」のプログラマーと話していました(私自身はプロのプログラマーです)。私たちは彼の仕事のいくつかに出くわしました、そして彼はいつも彼のデータベースのすべての列をクエリしていると言いました(本番サーバー/コード内/内でも)。

私はそうしないように彼を説得しようとしたが、まだそれほど成功していなかった。私の意見では、プログラマーは、「かわいさ」、効率、およびトラフィックのために実際に必要なものだけを照会する必要があります。私は自分の考えを間違っていますか?

98
the baconing

何が返ってくるのか、それらをコード内の変数にどのようにバインドするのかを考えてください。

ここで、直接使用していない場合でも、誰かがテーブルスキーマを更新して列を追加(または削除)するとどうなるかを考えてみましょう。

コードのクエリを作成するときではなく、手動でクエリを入力するときにselect *を使用しても問題ありません。

67
gbjbaanb

スキーマの変更

  • 順序でフェッチ---コードがデータを取得する方法として列をフェッチしている場合、スキーマの変更により列番号が再調整されます。これはアプリケーションを台無しにし、悪いことが起こります。
  • 名前でフェッチ---コードがfooなどの名前で列をフェッチしていて、クエリの別のテーブルが列fooを追加する場合、これを処理する方法で、 rightfoo列を取得します。

どちらの方法でも、スキーマの変更により、データの抽出で問題が発生する可能性があります。

使用されていた列がテーブルから削除済みであるかどうかをさらに検討します。 _select * from ..._は引き続き機能しますが、結果セットからデータをプルしようとするとエラーになります。クエリで列が指定されている場合、queryはエラーになり、代わりに問題がどこにあるかを明確に示します。

データのオーバーヘッド

一部の列には、大量のデータが関連付けられている場合があります。 _*_を選択すると、データがプルされますall。はい、これは、選択した1000行のvarchar(4096)であり、不要な4 MBの追加データを提供しますが、とにかくネットワーク経由で送信されます。

スキーマの変更に関連して、そのvarcharは、最初にテーブルを作成したときに存在しなかった可能性がありますが、現在は存在しています。

意図を伝えられない

_*_を選択して20列を取得しても2列しか必要ない場合は、コードの意図を伝えていません。 _select *_を実行するクエリを見ると、その重要な部分が何であるかわかりません。これらの他のプランを使用するようにクエリを変更して、これらの列を含めないことでクエリを高速化できますか?クエリが返すものの意図が明確でないため、わかりません。


それらのスキーマの変更をもう少し詳しく調べるSQLフィドルを見てみましょう。

まず、初期データベース: http://sqlfiddle.com/#!2/a67dd/1

DDL:

_create table one (oneid int, data int, twoid int);
create table two (twoid int, other int);

insert into one values (1, 42, 2);
insert into two values (2, 43);
_

SQL:

_select * from one join two on (one.twoid = two.twoid);
_

そして、返される列は_oneid=1_、_data=42_、_twoid=2_、および_other=43_です。

では、テーブル1に列を追加するとどうなりますか? http://sqlfiddle.com/#!2/cd0b0/1

_alter table one add column other text;

update one set other = 'foo';
_

そして、以前と同じクエリからの結果は、_oneid=1_、_data=42_、_twoid=2_、および_other=foo_です。

テーブルの1つを変更すると、_select *_の値が混乱し、突然「その他」がintにバインドされてエラーがスローされ、理由がわかりません。

代わりにSQLステートメントが

_select 
    one.oneid, one.data, two.twoid, two.other
from one join two on (one.twoid = two.twoid);
_

表1への変更は、データを破壊しなかったでしょう。そのクエリは、変更前も変更後も同じように実行されます。


索引付け

_select * from_を実行すると、all行がプルされますall条件に一致するテーブル。本当に気にしないテーブルでも。これはより多くのデータが転送されることを意味しますが、スタックのさらに下に潜む別のパフォーマンスの問題があります。

インデックス。 (SOに関連: selectステートメントでインデックスを使用する方法?

多数の列をプルバックする場合、データベースプランオプティマイザmayとにかくインデックスの使用を無視します。これらの列をすべてフェッチする必要があるため、インデックスを使用してフェッチするのに時間がかかるためです。完全なテーブルスキャンを実行するだけの場合よりも、クエリ内のすべての列。

ユーザーの姓を選択するだけの場合(多くの作業を行うため、インデックスが作成されます)、データベースはインデックスのみのスキャンを実行できます( postgres wiki index only scanmysqlフルテーブルスキャンvsフルインデックススキャンインデックスのみのスキャン:テーブルアクセスの回避 )。

可能であれば、インデックスからの読み取りのみに関するかなりの最適化があります。情報は、各インデックスページでより速く取得できます。これは、取得する情報が少なくなるためです。_select *_の他のすべての列を取得するわけではありません。インデックスのみのスキャンが100xのオーダーの結果を返す可能性がありますfaster(ソース: Select * is bad )。

これは、フルインデックススキャンが優れていると言っているわけではありませんが、それでもフルスキャンですが、フルテーブルスキャンよりは優れています。 _select *_がパフォーマンスを低下させるすべての方法を追跡し始めると、新しい方法を見つけ続けます。

関連読書

179
user40980

また、JOINクエリであり、クエリ結果を連想配列に取得している場合(PHPの場合と同様)、バグが発生しやすくなります。

ことは

  1. テーブルfooに列idnameがある場合
  2. テーブルbarに列idおよびaddressがある場合、
  3. あなたのコードではSELECT * FROM foo JOIN bar ON foo.id = bar.idを使用しています

誰かがnameテーブルにbar列を追加するとどうなるかを推測します。

name列が結果twiceに表示され、結果を配列に格納している場合、2番目のnamebar.name)のデータが最初のnamefoo.name)!

それは非常に自明ではないので、それはかなり厄介なバグです。理解するのにしばらく時間がかかる場合があり、テーブルに別の列を追加する人がそのような望ましくない副作用を予期することはあり得ません。

(実話)。

したがって、*を使用しないでください。取得する列を制御し、必要に応じてエイリアスを使用してください。

38
Konrad Morawski

すべての列のクエリは、多くの場合、完全に正当な場合があります。

常にすべての列をクエリするのはそうではありません。

実際にデータを取得してデータを送り返すという実際のビジネスに取り掛かる前に、処理する必要のある列を特定するために、内部メタデータに取り掛かる必要があるデータベースエンジンの作業が増えます。 OK、それは世界最大のオーバーヘッドではありませんが、システムカタログはかなりのボトルネックになる可能性があります。

1つまたは2つのフィールドのみが必要な場合に任意の数のフィールドをプルバックするため、これはネットワークにとってより多くの作業です。誰かが[else]に行って数十の追加フィールドを追加すると、そのすべてに大きなテキストのチャンクが含まれている場合、スループットは突然明らかになります-明白な理由はありません。これは、 "where"句が特に適切でなく、多くの行もプルバックしている場合に悪化します。これは、大量のデータがネットワーク全体を行き来している可能性があるためです(つまり、遅くなります)。

それはあなたのアプリケーションにとってより多くの仕事であり、それがおそらく気にしていないこの余分なデータのすべてを引き戻して保存しなければならないのです。

列の順序が変わるリスクがあります。 OK、これについて心配する必要はありません(必要な列のみを選択した場合はしない)。しかし、一度にすべての列を取得し、誰か[else]がテーブル内の列の順序を並べ替えます。注意深く作成された、ホールにあるアカウントに与えたCSVエクスポートは、突然、すべてがポットに行き着きます-明白な理由はありません。

ところで、私は「誰か[その他]」を何度か言ってきました。データベースは本質的にマルチユーザーであることに注意してください。自分がやると思っていることを、自分で制御できない場合があります。

22
Phill W.

簡単に言えば、使用するデータベースによって異なります。 Relationalデータベースは、高速で信頼性の高いatomicの方法で必要なデータを抽出するように最適化されています。大規模なデータセットと複雑なクエリでは、SELECTING *よりもはるかに高速で、おそらく安全であり、「コード」側で結合と同等の処理を実行します。 Key-Valueストアには、そのような機能が実装されていないか、本番環境で使用するのに十分成熟していない場合があります。

つまり、SELECT *で使用しているデータ構造にデータを入力し、残りをコードで解決することはできますが、スケーリングする場合はパフォーマンスのボトルネックが見つかります。

最も近い比較はデータのソートです。クイックソートまたはバブルソートを使用でき、結果は正しくなります。ただし、最適化は行われず、同時実行性を導入してアトミックにソートする必要がある場合は間違いなく問題が発生します。

もちろん、RAMとCPUを追加する方が、SQLクエリを実行でき、JOINが何であるかを漠然と理解しているプログラマーに投資するよりも安価です。

11
lorenzog

IMO、明示的であるか暗黙的であるかについて。コードを書くとき、すべてのパーツがたまたまそこにあるからというだけでなく、私がそれを機能させたので、それを機能させたいと思っています。すべてのレコードをクエリしてコードが機能する場合は、先に進む傾向があります。後で何かが変更されてコードが機能しなくなった場合、そこにあるはずの値を探す多くのクエリと関数をデバッグするのは非常に大変であり、参照される値は*だけです。

また、N層のアプローチでは、データベーススキーマの中断をデータ層に分離することが依然として最善です。データ層がビジネスロジックに*を渡し、プレゼンテーション層にある可能性が最も高い場合は、デバッグスコープを指数関数的に拡大しています。

8
zkent

テーブルが新しい列を取得すると、不要な場合でもすべての列が取得されるためです。 varcharsを使用すると、DBから移動する必要のある追加のデータが大量になる可能性があります

一部のDB最適化では、select *を使用して固定長部分へのアクセスを高速化するために、非固定長レコードを別のファイルに抽出することもありますが、その目的は無効になります。

6
ratchet freak

オーバーヘッド以外に、そもそも避けたいことは、プログラマーとして、データベース管理者が定義した列の順序に依存しないことです。すべて必要な場合でも、各列を選択します。

1

ビルドの目的で使用してはならない理由はわかりません。データベースからすべての列を取得してください。私は3つのケースを見ます:

  1. 列がデータベースに追加され、コードでも必要になります。 a)With *は適切なメッセージで失敗します。 b)*なしでは機能しますが、予想外の結果は得られません。

  2. 列がデータベースに追加され、コードでそれを必要としません。 a)*を使用すると失敗します。これは、セマンティクスが「すべて取得」を意味するため、*は適用されないことを意味します。 b)*なしで機能します。

  3. 列が削除されるコードはいずれかの方法で失敗します。

現在、最も一般的なケースはケース1です(あなたが*を使用したため、これはおそらくすべてがすべて欲しいということを意味します)。 *を使用しないと、正常に機能するが期待どおりに動作しないコードが存在する可能性があります。これは、適切なエラーメッセージで失敗するコードの最悪の場合です。

私の考えではエラーが発生しやすい列インデックスに基づいて列データを取得するコードは考慮していません。列名に基づいて検索する方がはるかに論理的です。

1
m3th0dman

このように考える...小さな文字列または数値フィールドがいくつかあるテーブルのすべての列をクエリすると、合計100kのデータになります。悪い習慣ですが、実行されます。次に、たとえば画像や10MBのWord文書を保持する単一のフィールドを追加します。これで、フィールドがテーブルに追加されただけで、高速で実行されるクエリがすぐに不思議なほどパフォーマンスが低下します。巨大なデータ要素は必要ないかもしれませんが、Select * from Tableとにかくそれを取得します。

1
kevin mitchell