As several have commented, there are two likely suspects here: changing plan and time to return the rows. For such a simple query, it seems most likely that the time to deliver the rows over the network is the culprit.
So it would be useful to perform some tests to see how long it takes to get the full data set back in your client (e.g. SQL*Plus) vs. within PL/SQL. There are many ways to achieve this, and a resourceful developer will surely find a suitable way. I provide one method here that uses V$ tables to get a fairly clear picture of what Oracle is doing.
One way to gain insight into the impact of network slowness on your query is by inspecting the various wait events generated by your session.
Note: You need access to V$SESSION_EVENTS
for this particular method to work.
Connect to the database using your tool of choice and get the current session ID:
SELECT SYS_CONTEXT ('USERENV', 'SID') FROM DUAL;
Now run the very slow query, making sure to bring back all rows.
If you are using SQL*Plus, you might want to use SET AUTOTRACE TRACEONLY
in order to see the plan and stats, but not see the data flying by. SQL*Plus will still pull the rows across the network, it will simply keep from displaying them.
Once your slow query completes go into another connection and run this query:
select wait_class, event, total_waits, time_waited from v$session_event where sid = <SID from first query>;
Here is an example of the results after I ran an intentionally data-volume-heavy query:
WAIT_CLASS EVENT TOTAL_WAITS TIME_WAITED
----------- --------------------------------- ----------- -----------
User I/O Disk file operations I/O 8 0
Application enq: KO - fast object checkpoint 1 0
Commit log file sync 2 0
User I/O db file sequential read 147 9
User I/O db file scattered read 491 50
User I/O direct path read 146 6
Network SQL*Net message to client 2072 0
Network SQL*Net more data to client 618 8
Idle SQL*Net message from client 2071 50067
Other events in waitclass Other 1 0
Times are in 1/100 sec.
Of interest is the time spent in "SQL*Net message from client"...that represents time waiting for SQL*Plus to say "thanks for those rows, please send me more"
Times listed as "User I/O" are related to database reads. "System I/O" is related to database writes.
In this example, it is clear that the lion's share of the time is being spent sending the rows to SQL*Plus.
Note that these times don't account for everything: the total time your SQL is running consists of waits and CPU, so this set of event waits doesn't show any CPU time. But if we are only interested in the relative times of different wait events this does a pretty good job of it.
In addition, the wait times are cumulative for the entire session, so be sure that the test session is connected at the time you run the test SQL, and check the V$SESSION_EVENTS
data immediately, as more wait time might be accumulated that is irrelevant to your test.