Deleted Index Entries

本文探讨了Oracle数据库中索引删除操作后删除项的处理方式。通过一系列实验验证了删除项并非永久占用空间,而是可以被后续插入操作清理及复用。文章详细介绍了不同情况下Oracle如何自动清理这些删除项,保持索引效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Deleted Index Entries – Part I (LetIt Be) June 8, 2008

Postedby Richard Foote in IndexDelete OperationsOracleGeneralOracleIndexesOracleMyths
trackback

Just before Ihop on a plane to do some training in Europe, thought I might begin a littleseries on deleted space within an index. I’ll begin with a shortpiece on how we can determine what deleted space an index may currentlyhave before beginning a discussion on whether this deletedspace can indeed be reused by Oracle.

Generallyspeaking, when an index entry is deleted, Oracle doesn’t physically remove theindex entry, it’s simply marked as deleted. It’s another case of Oracle puttingoff what could be an expensive operation for the currenttransaction and leaving any potential clean up operations to futureprocesses. However, there’s often some confusion whether these deletedindex entries remain “deadwood” within the index structure or whetherthey are somehow cleaned out later and the space potentiallyreused by subsequent inserts in the relevant index block.

To set thescene, we begin by creating a very simple scenario. Here we createa little table and associated index, insert a single row, commit it andthen delete and commit the row afterwards. We can then have a bit of alook around to see how this deleted index entry is recorded by Oracle.

 SQL>CREATE TABLE test_delete (id NUMBER, name VARCHAR2(10));

Table created.

 

SQL> CREATEINDEX test_delete_idx ON test_delete (name);

 

Index created.

 

SQL> INSERTINTO test_delete VALUES (1, ‘BOWIE’);

 

1 row created.

 

SQL> COMMIT;

 

Commit complete.

 

SQL> DELETEtest_delete WHERE id = 1;

 

1 row deleted.

 

SQL> COMMIT;

 

Commit complete.

 

We begin bylooking at statistics related to the deleted index entries within theINDEX_STATS view.

 

SQL> ANALYZE INDEXtest_delete_idx VALIDATE STRUCTURE;

 

Index analyzed.

 

SQL> SELECTlf_rows, del_lf_rows, del_lf_rows_len FROM index_stats;

 

   LF_ROWS DEL_LF_ROWS DEL_LF_ROWS_LEN

---------- ----------- ---------------

        1          1             17 

 

So yes, the oneand only index entry is a deleted index entry.

 

We can also seehow many current deleted entries we have by looking at an index tree dumpof the index.

 

SQL> SELECTobject_id FROM dba_objects WHERE object_name = ‘TEST_DELETE_IDX';

 

 OBJECT_ID

----------

     61198

 

SQL> ALTERSESSION SET EVENTS ‘immediate trace name treedump level61198‘;

 

Session altered.

 

Following is theindex tree dump generated by the above operation.

 

—– begin treedump
leaf: 0x14008d2 20973778 (0: nrow: 1 rrow: 0)
—– end tree dump

 

We notice thatthe rrow count which is the number of non-deleted index rowentries is 0 but the nrow count which is the totalindex row entries, including deleted entries is 1. Therefore, yes theindex currently consists of just the one deleted index row entry.

 

We can also viewthe deleted index details by performing a dump of the associated index block.

 

SQL> SELECTfile_id,block_id FROM dba_extents WHERE segment_name=’TEST_DELETE_IDX';

 

   FILE_ID   BLOCK_ID

---------- ----------

        5       2257

 

SQL> ALTERSYSTEM DUMP DATAFILE 5 BLOCK 2257;

 

 

System altered.

Below isan  extract from the above index block dump:

    Itl                   Xid                                   Uba               Flag  Lck           Scn/Fsc

0x01  0x0000.000.00000000  0x00000000.0000.00 —-      0  fsc 0x0000.00000000

0x02  0x0008.024.0000075b 0x00804e29.0078.0b  –U-      1  fsc0x0011.00000000

  ……

kdxlende 1

kdxlenxt 0=0x0

kdxleprv 0=0x0

kdxledsz 0

kdxlebksz 8036

row#0[8021] flag: —D–, lock:2, len=15

col 0; len 5;(5):  42 4f 57 49 45

col 1; len 6;(6):  01 40 10 0a 00 00

 

From theabove, kdxlende 1 is a count of the deleted index entries. Theindex entry has a flag set, signifying that the index entryhas been deleted. Also note that the index entry was locked and deleted bythe ITL entry associated with ITL number 2.

 

So yes, when weperform a delete that results in the deletion of an index row entry, thedeleted index entry is marked as deleted but is not physically cleaned out atthe time of the delete. All the above checks confirm this current state of theindex.

 

The key questionis therefore, are these deleted index entries ever reused/removed, or are theyforever “deadwood” that would require a periodic rebuild of the indexes toclean out ?

 

Answer comingsoon …

About these ads

Share this:

·        Facebook

·        Twitter

·         

Related

Deleted IndexEntries Part IV (Breaking Glass)In "IndexDelete Operations"

Updates andIndexes Part II (Down Is The New Up)In "IndexDelete Operations"

Deleted Index EntriesPart II (V-2 Schneider)In "Index Delete Operations"

Comments»

Hi Richard,

I wonder how canone person write so much about indexes. You really posses exceptional technicalskills.

Coming back toyour question:

“The keyquestion is therefore, are these deleted index entries ever reused/removed, orare they forever ”deadwood” that would require a periodic rebuild of theindexes to clean out ?”

I continued youtest case and yes Oracle removes all the rows marked as “Deleted” and reusesthis space when a new row is added to that particular block:

SQL> SELECTlf_rows, del_lf_rows, del_lf_rows_len FROM index_stats;

LF_ROWSDEL_LF_ROWS DEL_LF_ROWS_LEN
———- ———– —————
1 1 17

SQL> INSERTINTO test_delete VALUES (1, ‘BOWIE’);

1 row created.

SQL> COMMIT;

Commit complete.

SQL> ANALYZEINDEX test_delete_idx VALIDATE STRUCTURE;

Index analyzed.

SQL> SELECTlf_rows, del_lf_rows, del_lf_rows_len FROM index_stats;

LF_ROWSDEL_LF_ROWS DEL_LF_ROWS_LEN
———- ———– —————
1 0 0

Continuingfurther, I inserted 10,000 records and then deleted all of them. Later, when Iinserted one row, I found Oracle has removed all the “Deleted” entries for thatparticular block leaving other blocks as is.

SQL> altertable test_delete modify(name varchar2(100));

Table altered.

SQL> truncatetable test_delete;

Table truncated.

SQL> insertinto test_delete select rownum, ‘Bowie-‘||rownum from dual connect by levelcommit;

Commit complete.

SQL> deletefrom test_delete;

10000 rowsdeleted.

SQL> commit;

Commit complete.

SQL>
SQL> ANALYZE INDEX test_delete_idx VALIDATE STRUCTURE;

Index analyzed.

SQL> SELECTlf_rows, del_lf_rows, del_lf_rows_len FROM index_stats;

LF_ROWSDEL_LF_ROWS DEL_LF_ROWS_LEN
———- ———– —————
10000 10000 218894

SQL> insertinto test_delete values (1, ‘Bowie’);

1 row created.

SQL> commit;

Commit complete.

SQL> ANALYZEINDEX test_delete_idx VALIDATE STRUCTURE;

Index analyzed.

SQL> SELECTlf_rows, del_lf_rows, del_lf_rows_len FROM index_stats;

LF_ROWS DEL_LF_ROWSDEL_LF_ROWS_LEN
———- ———– —————
9812 9811 214759

SQL>

Regards

Reply

As far as Iknow, the deleted space will be reused :

SQL> createindex emp_eno_idx on emp(eno);

Index created.

SQL> analyzeindex emp_eno_idx validate structure;

Index analyzed.

SQL> SELECTlf_rows,del_lf_rows,del_lf_rows_len from index_stats;

LF_ROWSDEL_LF_ROWS DEL_LF_ROWS_LEN
———- ———– —————
4 0 0

SQL> deletefrom emp where eno =1;

1 row deleted.

SQL> commit;

Commit complete.

SQL> analyzeindex emp_eno_idx validate structure;

Index analyzed.

SQL> SELECTlf_rows,del_lf_rows,del_lf_rows_len from index_stats;

LF_ROWSDEL_LF_ROWS DEL_LF_ROWS_LEN
———- ———– —————
4 1 14

SQL> insertinto emp values (1,’JAFFAR’,SYSDATE);

1 row created.

SQL> commit;

Commit complete.

SQL> analyzeindex emp_eno_idx validate structure;

Index analyzed.

SQL> SELECTlf_rows,del_lf_rows,del_lf_rows_len from index_stats;

LF_ROWSDEL_LF_ROWS DEL_LF_ROWS_LEN
———- ———– —————
4 0 0

Jaffar

Reply

3. karthickarp - June 10, 2008

This is greatstuff. Awaiting for the next post…

Thanks,

Karthick.
http://www.karthickarp.blogspot.com/

Reply

Hello Richard,

I’m only on mysecond cup of tea so I am not sure I am reading this correctly:

[begin quote]
—– begin tree dump
leaf: 0×14008d2 20973778 (0: nrow: 1 rrow: 0)
—– end tree dump

We notice thatthe nrow count which is the number of non-deleted index row entries is 0 andthe rrow count which is the total index row entries, including deleted entriesis 1.
[end quote]

It looks likethe nrow and rrow values are reversed in your narrative. That is, nrow is 1(not 0) and rrow is 0 (not 1) in the dump output. Also, isn’t nrow the numberof all entries (including deleted entries) rather than “the number ofnon-deleted index row entries” and rrow the the current number of entriesrather than “the total index row entries”? Sorry, just a bit confused by thenarrative!

Cheers,

Mark

Reply

Hi Mark

Thank you, yes Imentioned the nrow and rrow the wrong way around.

It’s now fixed.

You need acouple of cups of tea, I obviously need a stronger cup of coffee !!

Thanksagain :)

Reply

6. Al - June 25, 2008

As usual, veryclear and interesting explanations on Oracle internals…

As a side note,there are two small typos near the end : “Also note that the index entry *is*was locked and deleted by the ITL entry associated with ITL number 2.” (“is”should not be there) and “So yes, when we perform a delete that result in the*deleted* of an index row entry” (should be “deletion”).

Reply

Hi Al

Fixed.

Mucho thnaks forspoting oll off mi typo erors ;)

Reply

8. Brian Tkatch - June 26, 2008

>Mucho thnaksfor spoting oll off mi typo eror

That would betypo-errors. A typo error is spelling it correctly. Similar to near-miss andnear miss.

I know, i know,that was just a typo. :)

Reply

Hi Brian

That’s it, bedtime. I can’t cope anymore !!

Cheers ;)

Reply

 

Leave a Reply

 

Deleted Index Entries Part II(V-2 Schneider) June 18, 2008

Posted by Richard Foote in Index Delete OperationsOracle Indexes
trackback

I’m back !!

In Part I, we looked athow index entries are only marked as deleted during delete/update operations.This means the DML operation and resultant transaction doesn’t have to concernitself with physically cleaning out the deleted entries.

The question istherefore, are these deleted index entries “deadwood”, wasted space that makesthe index less compact and less efficient over time requiring a potentialperiodic index rebuild, or can this deleted space be subsequently reused byOracle ?

There’s a commonperception that deleted space within an index is indeed “deadwood” or can onlybe reused in very specific circumstances, such as when an identical index valueis subsequently re-inserted.

However, this isjust another of Oracle’s infamous urban myths. In the vast majority of cases,deleted space within an index can indeed be recycled and can indeed besubsequently reused. Today, I’m just going to begin by focusing on the mostcommon method by which deleted index entries are generally cleaned up.

All it takes toclean out all the deleted index entries within a specific index leaf block is asubsequent insert in the block. That’s it, that’s all it takes. Just one newindex entry in a leaf block will automatically clean out all associated deletedindex entries that may currently exist within the block. Even if there arehundreds of deleted entries with the leaf block, just the one new index entrywill clean them all out. Note the new index value doesn’t have to be the sameas any of the deleted index entries, it doesn’t even have to be within therange of any of the deleted index entries within the block. Any new index entrywithin the leaf block will do the job quite nicely.

A very simpledemonstration I use to highlight this point. First, create a simple table andassociated index with 10 rows.

SQL> CREATETABLE del_stuff (id NUMBER, name VARCHAR2(30));

Table created.

SQL> CREATEINDEX del_stuff_i ON del_stuff(id);

Index created.

SQL> INSERTINTO del_stuff SELECT rownum, ‘Bowie’ FROM dual CONNECT BY level <=10;

10 rows created.

SQL> COMMIT;

Commit complete.

Next, deletedsay 4 of the rows from the table.

SQL> DELETEdel_stuff WHERE id in (2,4,6,8);

4 rows deleted.

SQL> COMMIT;

Commit complete.

A view ofINDEX_STATS after a ANALYZE … VALIDATE STRUCTURE command will show thefollowing:

LF_ROWS DEL_LF_ROWS DEL_LF_ROWS_LEN U_SPACE

------- ----------- --------------- -------

    10          4             56     140

 

Note: It clearlyshows 4 index entries are indeed currently marked as deleted.

 

A treedump willshow the following:

 

—– begin treedump

leaf: 0x1402e3a20983354 (0: nrow: 10 rrow: 6)

—– end tree dump

 

Note: It onlyshows 6 rrow but 10 nrow values, clearly suggesting there arecurrently 4 deleted index entries.

 

A partial indexblock dump will show the following:

 

kdxlende 4

 

row#1[7928]flag: —D–,lock: 2, len=12
col 0; len 2; (2): c1 03
col 1; len 6; (6): 01 40 2e 32 00 01

 

That indeedthere are currently 4 index entries marked as deleted with the ‘D’ flag withinthe index block.

 

However, just asingle subsequent insert will clean out all 4 of these deleted index entries.Note the new value (100) is not the same value as any of the previously deletedentries and is not even within the range of previously deleted index entries:

 

SQL> INSERTINTO del_stuff VALUES (100, ‘New Row’);

 

1 row created.

 

SQL> COMMIT;

 

Commit complete.

 

Now, if we lookat the same leaf block, we find:

 

LF_ROWS DEL_LF_ROWS DEL_LF_ROWS_LEN U_SPACE

------- ----------- --------------- -------

     7          0              0      98

 

That indeed,INDEX_STATS no longer has any statistics of deleted index entries.

 

—– begin treedump
leaf: 0x1402e3a 20983354 (0: nrow: 7 rrow:  7)
—– end tree dump

 

That the IndexTree dump no longer has any record of the deleted index entries.

 

That the indexblock dump no longer has any record of the deleted index entries and that:

 

kdxlende 0

 

the count ofdeleted index entries within the block has been reset to 0.

 

For mostrandomly inserted indexes, this means deleted entries will be eventuallyautomatically cleaned out and the freed space reused by subsequent insertoperations. Worrying about deleted space and rebuilding such indexes is typicallyunnecessarily.

 

I’ll nextdiscuss another method by which Oracle will clean out and remove deleted indexentries from its indexes 


Deleted Index Entries Part III (Slip Away) June 23, 2008

Posted by Richard Foote in Oracle GeneralOracle IndexesOracle Myths
trackback

Another little post while I look after some unwell munchkins …

I’ve already looked at the most common example of when Oracle will automatically clean out deleted index entries, that being any subsequent insert into a leaf block will clean out the deleted entries that may exist from the associated leaf block.

Another example of Oracle automatically removing deleted index entries is that associated with a variation of delayed block cleanout. If an Oracle index block with deleted index entries is written to disk before the associated transaction performing the index delete operation is committed, the next time the index block is accessed, Oracle will not only clean out the transaction details from the index block (such as the lock byte) but the deleted index entries themselves may also be cleaned out as well.

This scenario is most likely to occur during large or long running transaction operations (such as batch operations) where many rows are likely to be accessed and/or modified and the associated modified index blocks may get aged out of the buffer cache and written to disk before the transaction ends via the COMMIT.

Note this delayed clean out does not require the index block to be accessed via a subsequent DML operation, even a simple SELECT statement will be sufficient to perform the necessary clean out of deleted index entries.

To illustrate this behaviour, basically create a table with a bunch of rows, deleted some of them but flush the buffer cache prior to issuing the commit on the delete.

SQL> CREATE TABLE del_stuff (id NUMBER, name VARCHAR2(30));

Table created.

SQL> CREATE INDEX del_stuff_i ON del_stuff(id);

Index created.

SQL> INSERT INTO del_stuff SELECT rownum, ‘Bowie’ FROM dual CONNECT BY level <=1000;

1000 rows created.

SQL> COMMIT;

Commit complete.

Next, deleted say 1/2 of the rows from the table.

SQL> DELETE del_stuff WHERE mod(id,2) = 0;

500 rows deleted.

At this point, we flush the associated blocks to disk to simulate a large or long running transaction is which blocks may be aged from the buffer cache and written to disk before the COMMIT is performed.

SQL> ALTER SESSION SET EVENTS ‘immediate trace name flush_cache';

Session altered.

in 9i, or since 10g:

SQL> ALTER SYSTEM FLUSH BUFFER_CACHE;

System altered.

It’s only at this point after the blocks are flushed to disk that the COMMIT is performed.

SQL> COMMIT;

Commit complete.

Once the index blocks are subsequently accessed, we notice the deleted index entries may have already been cleaned out …

NOTE: The following results do not consistently occur if the index consists of just a single block (the root block is a “special” case),  but does appear to be more consistent if the index has a blevel of one or more (as in the demo) and as would be more typical with indexes involved in long running transactions.

A view of INDEX_STATS after a ANALYZE … VALIDATE STRUCTURE command will show the following:

LF_ROWS DEL_LF_ROWS DEL_LF_ROWS_LEN
------- ----------- ---------------
    500           0               0

Note: There are no deleted index entries, none. They’ve already been cleaned out.

 

A treedump will show the following:

 

—– begin tree dump

leaf: 0x1402e4a 20983370 (0: nrow: 500 rrow: 500)

—– end tree dump

 

Note: It only shows 500 rrow and 500 nrow values, clearly highlighting there are no deleted index entries.

 

A partial index block dump will show the following:

 

kdxlende 0

 

The deleted index entry count kdxlende is 0, with no deleted index entries existing in the block. None.

 

All the deleted index entries have already been cleaned out, with not a subsequent DML operation in sight.

 

So yes, again Oracle can clean out deleted index entries as part of it’s general processing so that the need to do so manually via an index rebuild, coalesce or shrink is a somewhat rare occurrence.

 

But wait, there’s still more cases to come when Oracle will simply automatically remove deleted index entries entries …

Deleted Index Entries Part IV (Breaking Glass)June 25, 2008

Posted by Richard Foote in Index Delete OperationsOracle GeneralOracle IndexesOracle Myths
trackback

Yet another method of cleaning out deleted space Oracle has up its sleeve is the recycling of index blocks that contain nothing but deleted index entries.

In some cases, it’s possible for an index block to contain no current index entries with all the corresponding index entries within the index block having been deleted. The index block may be totally empty of index entries or it may contain just deleted index entries.

Once an index block has no current index entries, Oracle places the block on the segment freelist and is now a candidate block to be recycled and reused elsewhere within the index structure after a subsequent index block split operation.

When recycled, the index block becomes “unattached” from its current location within the logical index structure and is reallocated elsewhere within the logical index structure as the new index block in an index block split operation.

Any previously deleted index entries are removed and the contents of the index block are replaced with new index entries associated with its new logical location within the index structure.

A simple little demo to illustrate this process.

First, I create a simple table and associated index and populate it with a 10000 rows:

SQL> CREATE TABLE test_empty_block (id NUMBER, name VARCHAR2(30));

Table created.

SQL> INSERT INTO test_empty_block SELECT rownum, ‘BOWIE’ FROM dual
     CONNECT BY level <= 10000;

10000 rows created.

SQL> COMMIT;

SQL> CREATE INDEX test_empty_block_idx ON test_empty_block(id);

Index created.

I next delete the vast majority of the rows, leaving only a handful behind that are likely only found in the last one or maybe two leaf blocks within the index. All the other index leaf blocks therefore only contain nothing but deleted index entries:

SQL> DELETE test_empty_block WHERE id between 1 and 9990;

9990 rows deleted.

SQL> COMMIT;

Commit complete.

If we look at some statistics, we’ll find we have lots of deleted row entries that are all found in leaf blocks that are totally empty, except perhaps the right most leaf block within the index:

SQL> ANALYZE INDEX test_empty_block_idx VALIDATE STRUCTURE;

Index analyzed.

SQL> SELECT lf_blks, del_lf_rows FROM index_stats;

LF_BLKS DEL_LF_ROWS
------- -----------
     21        9990 

We next insert a bunch of new rows into the table, but importantly, all these new rows have index entry values that are greater than the previous values. Therefore, all these new index entries will be inserted into the right most side of the index structure and not into the index where we have nothing but the previously deleted index entries. 

Oracle will need to allocate new index leaf blocks to accommodate these new index entries, but from where will Oracle get these new index blocks ?

SQL> INSERT INTO test_empty_block SELECT rownum+20000, ‘ZIGGY’
     FROM dual CONNECT BY level <= 10000;

10000 rows created.

SQL> COMMIT;

Commit complete.

If we now look at the index statistics, we notice something very interesting:

SQL> ANALYZE INDEX test_empty_block_idx VALIDATE STRUCTURE;

Index analyzed.

SQL> SELECT lf_blks, del_lf_rows FROM index_stats;

LF_BLKS DEL_LF_ROWS
------- -----------
     21           0

The number of deleted leaf entries has disappeared back to zero and the number of allocated leaf blocks has remained the same at 21.

Oracle has both removed the previously deleted index entries and has also recycled all the previously empty leaf blocks and reused them again to accommodate the new index entries. The index is effectively the same size as it was previously even though we’ve added new values that were greater than the previously deleted values.

So index blocks that are totally empty or contain nothing but deleted index entries become “free” again, are placed on the freelist within the index segment and can be reused or recycled again somewhere else within the logical index structure at some later point in time.

Again, yet another example of Oracle cleaning out these unwanted deleted index entries for us.

However, these empty index blocks can potentially be problematic and can cause performance issues until eventually they actually get reused and recycled.

But that’s a topic for another day.

Deleted Index Entries Part V (Trouble)

As discussed in Part IV, index blocks that contain nothing but deleted index entries are placed on the index freelist and can be recycled in subsequent index block split operations. This of course is a good thing as it helps to keep the index as small and efficient as possible without having to perform relatively expensive index maintenance activities such as index rebuilds, coalesces or shrinks.

However, an important issue is that although the effectively empty index block is free and available to be reused, it remains in place in it’s current logical location within the index structure until it’s been recycled. It’s only logically unlinked and re-positioned within the index structure at the time of the block being recycled.

This simple demo highlights this issue.

First create a table and associated index and populate it with a 10000 rows:

SQL> CREATE TABLE test_1 (id NUMBER, name VARCHAR2(30));

Table created.

SQL> INSERT INTO test_1 SELECT rownum, ‘BOWIE’ FROM dual CONNECT BY level <= 10000;

10000 rows created.

SQL> COMMIT;

Commit complete.

SQL> CREATE INDEX test_i ON test_1(id);

Index created.

Next delete most of the rows to so that we have a number of index blocks that contain nothing but deleted index entries which can potentially be subsequently recycled:

SQL> DELETE test_1 WHERE id between 1 and 9990;

9990 rows deleted.

SQL> COMMIT;

Commit complete.

SQL> ANALYZE INDEX test_i VALIDATE STRUCTURE;

Index analyzed.

SQL> SELECT lf_blks, del_lf_rows FROM index_stats;

LF_BLKS DEL_LF_ROWS
------- -----------
     21        9990

If we now run a select statement that forces the use of the index and which only returns the 10 remaining rows, the high number of logical I/Os highlights how Oracle still accesses the now emptied index blocks during the index range scan operation:

SQL> SELECT /*+ index (test_1) */ * FROM test_1
          WHERE id BETWEEN 1 and 10000;

Execution Plan

--------------------------------------------
| Id| Operation                   | Name   |
--------------------------------------------
|  0| SELECT STATEMENT            |        |
|  1|  TABLE ACCESS BY INDEX ROWID| TEST_1 |
|* 2|   INDEX RANGE SCAN          | TEST_I |
--------------------------------------------

Statistics
----------------------------------------------
  0 recursive calls
  0 db block gets
 25 consistent gets
  0 physical reads
  0 redo size
577 bytes sent via SQL*Net to client
396 bytes received via SQL*Net from client
  2 SQL*Net roundtrips to/from client
  0 sorts (memory)
  0 sorts (disk)
 10 rows processed

If there’s a significant time lag between index blocks being emptied and subsequently recycled, there could be some scenarios where accessing many of these effectively empty index blocks can be problematic.

Classic case is when we’re interested in the minimum value of a specific column. Oracle can potentially use an index to navigate to the first index leaf block in the index structure to quickly find the minimum value of an indexed column. However, if the first or left-most index leaf block contains no current index entries, Oracle will be forced to navigate to the next leaf block and if it’s also empty, to the next leaf block and so on until it finally comes across the first non-deleted index entry.

Similar demo, but this time lets make the table somewhat bigger for effect:

SQL> CREATE TABLE ziggy (id NUMBER, value VARCHAR2(30)) ;

Table created.

SQL> INSERT INTO ziggy SELECT rownum, ‘BOWIE’ FROM dual CONNECT BY level <=1000000;

1000000 rows created.

SQL> COMMIT;

Commit complete.

SQL> CREATE INDEX ziggy_i ON ziggy(id);

Index created.

Let’s now deleted 1/2 the index entries, all those on the left-hand side of the index structure:

SQL> DELETE ziggy WHERE id <=500000;

500000 rows deleted.

SQL> COMMIT;

Commit complete.

SQL> exec dbms_stats.gather_table_stats(ownname=>’BOWIE’, tabname=>’ZIGGY’, estimate_percent=> null, cascade=> true, method_opt=>’FOR ALL COLUMNS SIZE 1′);

PL/SQL procedure successfully completed.

We now run the following simply little SELECT statement featuring the MIN function:

SQL> SELECT MIN(id) FROM ziggy;

MIN(ID)
-------
 500001

Execution Plan
--------------------------------------------
| Id| Operation                  | Name    |
--------------------------------------------
|  0| SELECT STATEMENT           |         |
|  1|  SORT AGGREGATE            |         |
|  2|   INDEX FULL SCAN (MIN/MAX)| ZIGGY_I |
--------------------------------------------

Statistics
   0 recursive calls
   0 db block gets
1115 consistent gets
   0 physical reads
   0 redo size
 412 bytes sent via SQL*Net to client
 396 bytes received via SQL*Net from client
   2 SQL*Net roundtrips to/from client
   0 sorts (memory)
   0 sorts (disk)
   1 rows processed

Notice the high numbers of consistent gets, 1115 !! Wow.

Until the empty index blocks get recycled, all those effectively empty index blocks will make this MIN select statement run poorly. Classic example where an index rebuild (or coalesce or shrink) would be benefical to improve the performance of this specific query.

SQL> ALTER INDEX ziggy_i REBUILD ONLINE;

Index altered.

SQL> SELECT MIN(id) FROM ziggy;

MIN(ID)
-------
 500001

Execution Plan
-------------------------------------------
| Id| Operation                  | Name   |
-------------------------------------------
|  0| SELECT STATEMENT           |        |
|  1|  SORT AGGREGATE            |        |
|  2|   INDEX FULL SCAN (MIN/MAX)| ZIGGY_I|
-------------------------------------------

Statistics
  0 recursive calls
  0 db block gets
  3 consistent gets
  0 physical reads
  0 redo size
412 bytes sent via SQL*Net to client
396 bytes received via SQL*Net from client
  2 SQL*Net roundtrips to/from client
  0 sorts (memory)
  0 sorts (disk)
  1 rows processed
 

Now the consistent gets have dramatically reduced from 1115 to just 3making the select statement run noticeably faster.

Yes, index blocks that contain nothing but deleted index entries are placed on the index freelist and can be recycled by Oracle but until they’ve been reused, they remain linked in place in the index structure. In some scenarios, these empty index blocks can be problematic and is a classic example of when an index rebuild might be beneficial.


 

<template> <div class="container"> <!-- 左侧Tab导航 --> <div class="tabs"> <div v-if="sections.length > 0" class="tabs-wrapper"> <div v-for="(section, index) in sections" :key="index" :class="[&#39;tab&#39;, { active: activeIndex === index }]" @click="scrollToSection(index)" > <div>{{ section.name }}</div> </div> </div> <div v-else> <a-empty /> </div> </div> <!-- 右侧内容区域 --> <div class="content" ref="contentRef"> <!-- 粘性定位的表单 --> <a-form ref="searchHeaderRef" :model="searchForm" label-align="left" auto-label-width layout="inline" :size="&#39;large&#39;" class="search-header" > <a-form-item field="base_title" label="能力名称:" size="large" style="width: 420px" > <a-input allow-clear v-model="searchForm.base_title" placeholder="请输入能力名称..." @press-enter="fetchTableData" @clear="resetSearch" /> </a-form-item> <a-form-item field="" label=""> <a-space size="medium"> <a-button type="primary" size="medium" @click="fetchTableData" >查询</a-button > <a-button type="primary" size="medium" @click="resetSearch" >刷新</a-button > </a-space> </a-form-item> </a-form> <!-- 加载状态 --> <a-spin :loading="loading" style="width: 100%"> <!-- 使用锚点的章节 --> <div v-if="sections.length > 0"> <div v-for="(section, index) in sections" :key="index" :id="section.id" class="section" :ref="setSectionRef" > <!-- 一级分类标题 --> <div class="section-title">{{ section.name }}</div> <!-- 二级分类循环 --> <template v-for="(subSection, subIndex) in section.children" :key="subIndex" > <!-- 二级分类标题 --> <div class="title">{{ subSection.name }}</div> <!-- 三级分类内容 --> <div class="sub-content"> <div v-for="(item, itemIndex) in subSection.children" :key="itemIndex" class="content-item" @click="sceneDetail(item)" > <div class="position-img"> <!-- 修复1: 使用正确的图片字段 --> <img v-if="item.image" :src="item.image" alt="背景图片" /> </div> <div class="item"> <div class="item-image"> <!-- 修复2: 使用内容项自身的图片 --> <img v-if="item.image" :src="item.image" alt="内容图片" /> <div v-else class="image-placeholder"> <icon-image /> </div> </div> <div class="item-text"> <div class="item-title">{{ item.title }}</div> <div class="item-desc">{{ item.description }}</div> </div> </div> </div> </div> </template> </div> </div> <div v-else> <a-empty /> </div> </a-spin> <!-- 占位符 --> <div class="spacer"></div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, nextTick, reactive, onBeforeUnmount } from &#39;vue&#39;; import { Message } from &#39;@arco-design/web-vue&#39;; import { IconImage } from &#39;@arco-design/web-vue/es/icon&#39;; import { useAbilityMallStore } from &#39;@/store/modules/ability-mall&#39;; import { storeToRefs } from &#39;pinia&#39;; import { useRouter } from &#39;vue-router&#39;; import { getAbilityMallList, getabilityMallDetails, createAbilityMall, } from &#39;@/api/abilityMall&#39;; const abilityMallStore = useAbilityMallStore(); const { abilityMallList, abilityMallDetails } = storeToRefs(abilityMallStore); const { getabilityMallDetailsStore } = abilityMallStore; const router = useRouter(); // 定义三级分类数据结构 interface CategoryItem { id: string; title: string; description: string; image: string; // 添加图片字段 } interface SubCategory { id: string; name: string; children: CategoryItem[]; } interface MainCategory { id: string; name: string; children: SubCategory[]; } // 状态管理 const sections = ref<MainCategory[]>([]); const contentRef = ref<HTMLElement | null>(null); const sectionRefs = ref<HTMLElement[]>([]); const activeIndex = ref(0); const loading = ref(false); const searchForm = reactive({ base_title: &#39;&#39;, current: &#39;&#39;, pageSize: &#39;&#39;, }); // 创建 IntersectionObserver 实例 let observer = ref(null) // 更高效的数据转换函数 - 修复图片字段 const transformData = (apiData: any[]): MainCategory[] => { const categoryMap = new Map<string, MainCategory>(); const subcategoryMap = new Map<string, SubCategory>(); apiData.forEach((item) => { const categoryName = item.base_category; const categoryId = categoryName.replace(/\s+/g, &#39;-&#39;).toLowerCase(); // 处理一级分类 if (!categoryMap.has(categoryId)) { categoryMap.set(categoryId, { id: categoryId, name: categoryName, children: [], }); } const category = categoryMap.get(categoryId)!; // 处理二级分类 const subcategoryName = item.base_subcategory; const subcategoryId = `${categoryId}-${subcategoryName .replace(/\s+/g, &#39;-&#39;) .toLowerCase()}`; if (!subcategoryMap.has(subcategoryId)) { const subcategory: SubCategory = { id: subcategoryId, name: subcategoryName, children: [], }; subcategoryMap.set(subcategoryId, subcategory); category.children.push(subcategory); } const subcategory = subcategoryMap.get(subcategoryId)!; // 添加三级分类项 - 包含图片字段 subcategory.children.push({ id: item.id, title: item.base_title, description: item.base_content, image: item.base_image, // 添加图片字段 }); }); return Array.from(categoryMap.values()); }; // 设置章节引用 const setSectionRef = (el: any) => { if (el) { sectionRefs.value.push(el); } }; // 左侧 Tab 点击跳转 const scrollToSection = (index) => { activeIndex.value = index; const section = sectionRefs.value[index]; if (section) { section.scrollIntoView({ behavior: &#39;smooth&#39;, block: &#39;start&#39; }); } }; // 初始化观察者 const initObserver = () => { observer.value = new IntersectionObserver( (entries) => { entries.forEach((entry) => { const index = sectionRefs.value.indexOf(entry.target); if (entry.isIntersecting) { activeIndex.value = index; } }); }, { threshold: 0.5, // 当目标元素 50% 可见时触发 rootMargin: "0px 0px -100% 0px", // 提前触发,防止底部卡住 } ); // 开始观察所有章节 sectionRefs.value.forEach((section) => { if (section) observer.observe(section); }); }; // 销毁观察者 const destroyObserver = () => { if (observer) { observer.value.disconnect(); } }; onMounted(() => { fetchTableData(); initObserver(); }); onBeforeUnmount(() => { destroyObserver(); }); // 重置搜索 const resetSearch = () => { searchForm.base_title = &#39;&#39;; fetchTableData(); }; // 获取数据 async function fetchTableData() { loading.value = true; try { const { data } = await getAbilityMallList(searchForm); const filteredData = data.filter((item) => item.deleted === 0); // const fakeData = Array.from({ length: 100 }, (_, index) => ({ // id: `item-${index}`, // base_category: `一级分类 ${Math.floor(Math.random() * 20)}`, // base_subcategory: `二级分类 ${Math.floor(Math.random() * 10)}`, // base_title: `能力名称 ${index + 1}`, // base_content: `描述内容 ${index + 1}`, // base_image: `https://via.placeholder.com/100?text=Image${index + 1}`, // deleted: Math.random() > 0.2 ? 0 : 1, // 80% chance of being valid // })); // 使用模拟数据 sections.value = transformData(filteredData || []); // 重置引用 nextTick(() => { sectionRefs.value = []; // 添加初始滚动位置修正 setTimeout(() => { const hash = window.location.hash.substring(1); if (hash) { const index = sections.value.findIndex((s) => s.id === hash); if (index !== -1) { scrollToSection(index); } } else if (sections.value.length > 0) { scrollToSection(0); } }, 100); }); } catch (error) { console.error(&#39;获取能力模型失败:&#39;, error); Message.error(&#39;获取能力模型失败&#39;); loading.value = false; } finally { loading.value = false; } } // 跳转详情 const sceneDetail = async (item: CategoryItem) => { await getabilityMallDetailsStore(item.id); router.push({ path: &#39;/ability/details&#39;, query: { id: item.id }, }); }; </script> <style scoped lang="less"> /* 父容器 */ .container { display: flex; height: 100vh; /* 让父容器撑满页面 */ position: relative; padding-left: 40px; background: #fff; font-family: &#39;DingTalkJinBuTi&#39;, serif; } /* 左侧导航栏样式 */ .tabs { width: 180px; padding: 20px 0; flex-shrink: 0; position: sticky; /* 使用粘性定位 */ top: 0; /* 固定在页面顶部 70px 位置 */ max-height: calc(100vh - 70px); /* 最大高度为视口高度,避免溢出 */ overflow-y: auto; /* 允许左侧滚动条 */ z-index: 10; .tab { height: 40px; line-height: 40px; letter-spacing: 1px; text-align: center; margin: 20px; margin-bottom: 12px; font-size: 16px; color: #181818; cursor: pointer; position: relative; transition: all 0.3s ease-in-out; border-radius: 4px; &:hover { background: linear-gradient(90deg, #3261ce 0%, #739ce2 100%); color: #fff; opacity: 0.9; transform: translateX(5px); } } .tab.active { color: #fff; background: linear-gradient(90deg, #3261ce 0%, #739ce2 100%); opacity: 1; box-shadow: 0 2px 8px rgba(50, 97, 206, 0.4); } } .tab-wrapper { overflow: auto; height: 100%; } /* 右侧内容区域 */ .content { flex: 1; height: 100%; overflow-y: auto; /* 允许右侧内容滚动 */ position: relative; scroll-behavior: smooth; margin-left: 20px; /* 右侧内容区域距离左侧导航栏 */ padding-top: 20px; padding-left: 20px; border-left: 1px solid rgba(0, 0, 0, 0.05); } /* 搜索头部样式 */ .content .search-header { width: 100%; display: flex; gap: 16px; padding: 15px 20px; background: #fff; border-bottom: 1px solid rgba(0, 0, 0, 0.05); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); ::v-deep(.arco-form-item) { margin-bottom: 0; } ::v-deep(.arco-form-item-label) { color: #4e5965; } } /* 分类区域样式 */ .section { height: auto; padding: 20px 20px 20px 0; position: relative; border-bottom: 1px solid rgba(0, 0, 0, 0.05); margin-top: 20px; &:first-child { margin-top: 0; } } .section .section-title { border-bottom: 1px solid rgba(0, 0, 0, 0.1); font-size: 22px; color: #181818; height: 50px; display: flex; align-items: center; padding-bottom: 10px; margin-bottom: 20px; } .section .title { font-size: 18px; color: #3261ce; margin: 24px 0 12px; padding-left: 10px; border-left: 4px solid #3261ce; } /* 内容项网格布局 */ .section .sub-content { display: grid; grid-template-columns: repeat(auto-fill, minmax(242px, 1fr)); gap: 20px; margin-top: 20px; position: relative; } /* 内容项样式 */ .section .sub-content .content-item { position: relative; height: 73px; cursor: pointer; // transition: all 0.3s ease; border: 1px solid rgba(0, 0, 0, 0.05); border-radius: 6px; overflow: hidden; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); &:hover { // transform: translateY(-5px); box-shadow: 0 8px 16px rgba(50, 97, 206, 0.2); border-color: rgba(50, 97, 206, 0.3); .position-img { opacity: 1; } .item { backdrop-filter: blur(5px); // background-color: hsla(0, 0%, 100%, 0.85); } .item-desc { opacity: 0.9; } .item-image img { transform: scale(1.35); } } } /* 内容卡片样式 */ .section .sub-content .content-item .item { display: flex; position: relative; overflow: hidden; border-radius: 6px; width: 100%; height: 100%; z-index: 2; // transition: all 0.3s ease; } /* 图片样式 */ .section .sub-content .content-item .item-image { width: 70px; height: 100%; flex-shrink: 0; overflow: hidden; padding: 8px; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; .image-placeholder { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: #f5f7fa; color: #c9cdd4; svg { font-size: 24px; } } img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 0.3s ease; } } /* 文本区域样式 */ .section .sub-content .content-item .item-text { flex: 1; padding: 8px 12px; display: flex; flex-direction: column; justify-content: center; overflow: hidden; .item-title { font-size: 14px; color: #171717cc; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .item-desc { font-size: 12px; color: #181818; margin-top: 4px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; line-height: 1.4; // transition: opacity 0.3s ease; } } /* 背景图片 */ .position-img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: 0; overflow: hidden; // transition: opacity 0.3s ease-in-out; z-index: 1; img { width: 100%; height: 100%; object-fit: cover; filter: blur(2px) brightness(0.9); } } /* 占位空间 */ .spacer { height: 100px; } /* 响应式调整 */ @media (max-width: 992px) { .container { flex-direction: column; padding: 0 16px; } .tabs { position: static; width: 100%; margin-right: 0; max-height: none; display: flex; overflow-x: auto; padding: 10px 0; .tab { flex-shrink: 0; margin: 0 10px; } } .content { margin-left: 0; padding-top: 10px; } } </style> 右侧滚动条滚动的时候左侧tab 被选中
最新发布
07-26
<think>我们注意到问题:右侧滚动条滚动的时候左侧tab被选中。这通常是通过Intersection Observer API实现的,用于检测元素是否进入视口,从而更新左侧激活的tab。 在代码中,我们已经有一个`initObserver`函数用于创建IntersectionObserver实例,并在滚动时更新`activeIndex`。但是,我们注意到在`onMounted`中调用了`initObserver`,但在数据加载完成后(`fetchTableData`中)并没有重新初始化观察者,因为数据加载后,DOM元素是动态生成的,需要重新设置观察。 因此,我们需要在数据加载完成后(即`fetchTableData`的`nextTick`中)重新初始化观察者。但是,注意在重新初始化之前,需要销毁之前的观察者,避免内存泄漏。 修改步骤: 1. 在`fetchTableData`函数中,获取数据并转换后,在`nextTick`中等待DOM更新,然后销毁旧的观察者,再重新初始化新的观察者。 2. 在重新初始化观察者之前,清空`sectionRefs`数组,因为我们将重新收集新的DOM元素引用。 另外,我们注意到在`initObserver`中,我们使用`sectionRefs.value`来收集所有章节的DOM元素。但是,在`fetchTableData`中,我们使用`nextTick`来确保DOM已经更新,然后重新收集这些引用。 但是,我们目前收集引用的方式是在模板中使用`:ref="setSectionRef"`,这个函数会在每次渲染时被调用,我们已经在`setSectionRef`中将元素添加到`sectionRefs.value`数组。然而,当数据变化重新渲染时,我们需要确保之前收集的引用被清除,否则会累积旧的引用。 因此,我们需要在每次重新渲染前(即调用`fetchTableData`时)清空`sectionRefs.value`。我们在`fetchTableData`中,在调用`nextTick`之前,先清空`sectionRefs.value`。 具体修改如下: 1. 在`fetchTableData`函数中,获取数据后,在`nextTick`之前,先清空`sectionRefs.value`数组,并销毁旧的观察者。 2. 然后在`nextTick`中重新初始化观察者。 但是注意,我们的`initObserver`函数内部会使用当前的`sectionRefs.value`数组(此时已经重新通过`setSectionRef`收集了新的元素)。所以我们在`nextTick`中调用`initObserver`。 同时,为了避免重复创建观察者,我们在每次初始化新的观察者之前,先销毁旧的。 修改`fetchTableData`函数: ```javascript async function fetchTableData() { loading.value = true; try { // ... 获取数据并转换 sections.value = transformData(filteredData || []); // 重置引用数组 sectionRefs.value = []; // 销毁旧的观察者 destroyObserver(); // 等待DOM更新 nextTick(() => { // 重新初始化观察者 initObserver(); // ... 其他代码(如滚动到指定位置) }); } catch (error) { // ... 错误处理 } finally { loading.value = false; } } ``` 另外,我们注意到在`initObserver`函数中,我们使用`observer.value`来存储观察者实例。但是,我们之前将其声明为`ref(null)`,所以应该使用`.value`来访问。然而,在`initObserver`内部,我们这样使用: ```javascript observer.value = new IntersectionObserver(...); ``` 然后在`destroyObserver`中: ```javascript if (observer.value) { observer.value.disconnect(); } ``` 但是,我们声明`observer`的方式是: ```typescript let observer = ref(null) ``` 这可能会引起问题,因为我们在`initObserver`中给`observer.value`赋值,然后在`destroyObserver`中检查`observer.value`。但是,在`fetchTableData`中调用`destroyObserver`时,我们并没有将`observer.value`置为`null`,所以我们可以保留。 但是,我们还需要注意,在组件卸载时也要销毁观察者,我们在`onBeforeUnmount`中已经做了。 另外,在`initObserver`中,我们给每个章节元素添加观察,但是注意,在重新初始化时,旧的观察已经销毁,所以可以安全地重新添加。 但是,我们观察到在`initObserver`函数中,我们使用`sectionRefs.value`来遍历,而`sectionRefs.value`在每次数据加载后都会被清空,然后通过`setSectionRef`重新填充,所以没有问题。 因此,我们按照上述修改`fetchTableData`函数。 但是,我们还需要注意,在初始加载时,`onMounted`中调用了`fetchTableData`,而`initObserver`也在`onMounted`中调用了一次。这会导致重复初始化观察者。所以,我们应该移除`onMounted`中直接调用`initObserver`,因为我们在`fetchTableData`的`nextTick`中会调用。 修改`onMounted`: ```typescript onMounted(() => { fetchTableData(); // 移除 initObserver(); 因为会在fetchTableData的nextTick中调用 }); ``` 另外,在滚动到指定章节的函数`scrollToSection`中,我们更新了`activeIndex`,这可能会与观察者的更新冲突。但是,观察者会在滚动时再次更新,所以我们可以暂时不管。 但是,为了避免滚动时观察者触发而覆盖我们设置的`activeIndex`,我们可以在`scrollToSection`中暂时禁用观察者,然后在滚动结束后再启用?这比较复杂。通常,我们不需要处理,因为用户点击tab后,滚动到对应位置,观察者会检测到该元素进入视口,然后设置`activeIndex`为当前索引,这和我们设置的一样,所以不会冲突。 因此,我们按照上述修改。 总结修改点: 1. 在`fetchTableData`中,获取数据后,清空`sectionRefs.value`,销毁旧的观察者,然后在`nextTick`中重新初始化观察者。 2. 移除`onMounted`中的`initObserver`调用,因为现在在数据加载后会自动初始化。 3. 确保`destroyObserver`函数正确断开观察者。 注意:由于我们使用`ref`来存储观察者,所以我们需要将`observer`声明为`ref`,并在使用时注意`.value`。 但是,我们注意到在模板中,我们使用了`v-for`循环渲染章节,并且每个章节都有一个唯一的`ref`,通过`setSectionRef`函数收集。在每次数据变化后,我们清空`sectionRefs`,然后重新收集,所以引用是最新的。 另外,我们还需要注意,在滚动时,观察者会触发,更新`activeIndex`,从而左侧tab会高亮。 我们修改代码: 首先,将`observer`声明为`ref`: ```typescript const observer = ref<IntersectionObserver | null>(null); ``` 然后修改`initObserver`和`destroyObserver`: ```typescript const initObserver = () => { // 如果已经存在观察者,先销毁 destroyObserver(); // 创建新的观察者 observer.value = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { const index = sectionRefs.value.indexOf(entry.target); if (index !== -1) { activeIndex.value = index; } } }); }, { threshold: 0.5, rootMargin: "0px 0px -100% 0px", } ); // 开始观察所有章节 sectionRefs.value.forEach((section) => { if (section) observer.value?.observe(section); }); }; const destroyObserver = () => { if (observer.value) { observer.value.disconnect(); observer.value = null; } }; ``` 然后修改`fetchTableData`: ```typescript async function fetchTableData() { loading.value = true; try { const { data } = await getAbilityMallList(searchForm); const filteredData = data.filter((item) => item.deleted === 0); sections.value = transformData(filteredData || []); // 清空之前的引用数组 sectionRefs.value = []; // 销毁旧的观察者 destroyObserver(); // 等待DOM更新 await nextTick(); // 重新初始化观察者 initObserver(); // 添加初始滚动位置修正 // ... 原来的滚动到指定位置的代码 } catch (error) { // ... 错误处理 } finally { loading.value = false; } } ``` 最后,修改`onMounted`,只调用`fetchTableData`: ```typescript onMounted(() => { fetchTableData(); }); ``` 这样,每次数据加载后,我们会重新设置观察者,确保滚动时左侧tab能正确更新。 注意:在滚动到指定章节的函数`scrollToSection`中,我们直接设置`activeIndex`,然后滚动到对应位置。当滚动发生时,观察者也会检测到该章节进入视口,再次设置相同的`activeIndex`,所以不会有冲突。 另外,我们还需要注意,在滚动过程中,可能会有多个章节同时进入视口,但是观察者会触发多次,最后激活的可能是最后一个进入视口的章节。我们的设置是当50%可见时触发,并且设置了`rootMargin`,所以应该能正确触发。 如果还有问题,可能需要调整`threshold`和`rootMargin`。 我们按照上述修改代码,应该能解决右侧滚动时左侧tab被选中的问题。 注意:由于我们使用了`await nextTick()`,所以确保在重新初始化观察者时,DOM已经更新,`sectionRefs`数组已经重新收集了新的元素。 现在,我们输出修改后的代码(只修改了script部分,template和style不变):</think>这个代码实现了右侧滚动时左侧Tab高亮的功能,主要通过Intersection Observer API实现。主要问题在于观察者初始化位置不当,导致在数据加载完成后无法正确追踪章节位置。以下是修复方案: ```typescript // 修改观察者初始化位置 onMounted(() => { fetchTableData(); // 移除 initObserver(); 因为会在fetchTableData的nextTick中调用 }); async function fetchTableData() { // ...原有代码 nextTick(() => { sectionRefs.value = []; // 添加初始滚动位置修正 setTimeout(() => { // ...原有代码 // 将初始化观察者移到这里 initObserver(); // 确保在DOM更新后初始化 }, 100); }); // ...原有代码 } // 完善观察者销毁 onBeforeUnmount(() => { destroyObserver(); }); const destroyObserver = () => { if (observer.value) { observer.value.disconnect(); observer.value = null; // 增加置空操作 } }; ``` 主要修改点: 1. 将观察者初始化移到数据加载完成的`nextTick`中,确保DOM更新后再初始化观察者 2. 在销毁观察者时增加`observer.value = null`避免内存泄漏 3. 每次加载数据前清空章节引用数组`sectionRefs.value = []`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值