I have a recursive CTE inside a inline table-valued function. The ITVF is returning a list of IDs containing a long sequence of ancestors for a person, it usually loops back about 12 to 18 times before getting to the end. It's quite fast but there's an error in the estimations that stacks when used in on many people, so it becomes extremely slow.
The CTE looks like this
WITH ancestors AS (
SELECT
IndID,
AncestorID
FROM
dbo.persons
UNION ALL
SELECT
IndID,
AncestorID
FROM
ancestors a
INNER JOIN dbo.persons p ON p.IndID = a.AncestorID
)
SELECT IndID, AncestorID FROM ancestors
I have a dozen million rows so it's quite a large table. When I ask for one IndID, the execution plan says that it estimated 7 rows but got 1300 actual rows. For a single request it's acceptable (runs in less than a second) but if I join it in an other request so it gets called, let's say 100 times, the speed drops to a crawl since the estimation is getting worse and worse.
Just to be clear, the estimation error is present even out of the IVTF. I only specified it to be clear that I can't just use a temporary table. It needs to stay in a IVTF so I can join it in larger, more complex requests and it stays parallelable. What can I do to estimate the rows better?
Update : Paste The Plan
Update 2 : Less simplified
I'm kind of stuck between two problems. Either I use a MSTVF and all my queries can't parallelize, or I use an ITVF and hope that the SQL gods are generous and don't horribly underestimates the row counts so everything now swaps on the hard drive instead of RAM. I hope that it's just that I'm dumb and it's a stupid easy fix somewhere.
Update 3 To answer to the best of my knowledge to the questions asked.
uno) Updated to the latest cumulative update. Didn't change anything as expected, but it's good to be up to date as you said :)
dos) We are on Standard edition, but I do have a Column Store and I can't remember why I did it. It's on IndID, FirstNameID, LastNameID. I'll try dropping it, we are only 2 users on the database today, we can manage downtimes if it crashes something else.
After removing the ColumnStore, it did save about 30 seconds! Still slow, but it's better. I'll have to check my notes to find why I did that ColumnStore.
dos: part 2) The "underpowered box" feeling you got is exactly what got me up to now. I tought our machine was underpowered, but after talking with the IT here, they said we weren't using more than 25% of it's ressources available so the bottleneck was definitely at the SQL level. So I asked for an update from SQL 2017 to 2022 last month and then, now that I saw that most of my heavy queries were always running serialized, started optimizing until I hit this one. I tried the OPTION(USE HINT('DISALLOW_BATCH_MODE'), MAXDOP 8);
and I don't see any changes in speed.
tres) That request is indeed supposed to return about 14 million rows, so no worries on that side. But isn't the fact only 8 rows were estimated in the ressource reservation a reason why it's much slower than it should be?
more context) I was using a MSTVF before all my work this month, when I switched to an IVTF it is faster but the curve of time spent vs rows asked is exponential instead of linear if you get what I mean. I'm open to rethink how all this is done.
I work for a research group and a part of my job is extracting datasets for researchers. I'm pretty much the only heavy user on the database, my colleagues are more in the "inserting and cleaning the data" part of the job. So I can pretty much do what I want with the indexes, functions, etc. as long as the table structure itself is not changed too much.
Update 4 - What? I don't get it, I was trying to make a nice graphic to show the "time spent vs rows asked" exponential curve, so I changed my query to get nice square numbers.
select
count(*)
FROM
(SELECT TOP 10000 * FROM individus.Individus WHERE AnneeNaissance > 1901 AND AnneeDeces < 1911) i CROSS APPLY
individus.GetAscendanceSimple(i.IndID) a
And that ran in 10 seconds... Even tried TOP 10,000,000 and still fast, so I just have to put a arbitrary large number so all my cases are covered and it runs as fast as I would have hoped (The TOP is important). Before putting that as a solution, I must be wrong no? That's a really dumb fix if it's all we need to do to fix the planning.